How to Mock WebRTC, WebSocket, and TTS/STT Services in React Native Avatar Tests

Mock WebRTC, WebSocket, TTS, and STT in React Native avatar tests for deterministic session, transcript, and reconnect logic.
Introduction
React Native avatar tests are awkward because they span multiple real-time boundaries at once: WebRTC media, WebSocket signaling, and speech services for both input (STT) and output (TTS). If you test these paths against real network services every run, you get flaky tests, slow feedback, and hard-to-debug failures that have nothing to do with your UI or session logic.
The goal of this post is to show how to make those tests deterministic by replacing each external boundary with a focused mock or fake. By the end, you should be able to test avatar session startup, transcript-driven UI updates, reconnect behavior, and error handling in React Native without depending on a live media stack.
What you should mock, and what you should not
Start by separating the problem into layers:
WebSocket: signaling, session events, and control messages.
WebRTC: peer connection state, tracks, ICE candidates, and remote media arrival.
TTS/STT services: text-to-audio and audio-to-text behavior, usually behind HTTP or streaming APIs.
In tests, you generally want to mock the boundaries, not the app internals. That means your React component should still exercise its own state transitions, event handlers, and rendering logic. What it should not do is hit a real STT vendor, negotiate a real peer connection, or wait for an avatar video track from the network.
The useful rule is: if the dependency owns timing, network, codec behavior, or nondeterministic state, fake it. If your code owns UI state, event translation, or retry logic, test it directly.
Mocking WebSocket signaling in React Native
Most avatar flows begin with a signaling channel. The app connects, authenticates, requests a session, and then receives status messages such as connected, ready, transcript, or error. For component tests, a small event-emitting fake is usually enough.
In a test, mount your component, inject the mock socket, and drive state changes explicitly:
Two practical points matter here:
Model only the events your UI actually consumes. A realistic fake is better than a complete fake.
Keep the socket synchronous in tests unless you are explicitly verifying debounce, timeout, or retry behavior.
If your production code wraps a browser-like WebSocket API, you can also stub it at the global level in Jest. But for React Native, a dependency-injected socket object is usually easier to reason about than monkey-patching globals.
Faking WebRTC without trying to simulate the full stack
WebRTC is where many tests get overcomplicated. You do not need to implement ICE, SDP negotiation, codecs, or SRTP to validate your avatar UI. You need a predictable set of state transitions and media events:
signalingStatemoves through offer/answer flow.iceConnectionStatebecomes connected, failed, or disconnected.A remote track arrives and your UI starts rendering a video surface.
A good fake is a thin wrapper around the pieces your app reads. If your component listens for remote tracks, expose a method that fires an event resembling the real callback.
Then assert the behavior your app should show once a stream exists:
This approach is intentionally not a WebRTC simulator. That is the point. You want to exercise the code that binds peer connection state to React state, not duplicate browser networking in a test runner.
For edge cases, inject state transitions rather than trying to force them through protocol machinery. For example, test that your reconnect banner appears when connectionState becomes failed, or that your cleanup runs on disconnected. Those are logic tests, not media tests.
Mocking TTS and STT as deterministic stream processors
Voice agents tend to couple UI behavior to streaming speech results. STT usually emits partial hypotheses and a final transcript. TTS often returns audio chunks or a playable URL. In both cases, your tests should care about the sequencing of events, not the audio content itself.
For STT, use a fake event source that yields partial and final results in order:
That lets you validate common behaviors such as partial transcript rendering, send-button enablement, or “agent is thinking” transitions:
For TTS, you usually want one of two test modes:
Unit mode: stub the call and return a fixed “audio ready” response immediately.
Integration mode: simulate chunked delivery so you can verify buffering and playback state.
Keep in mind that avatar lip sync often depends on timing metadata, not just audio bytes. If your UI uses playback events to drive a talking indicator, mock those events directly. Do not try to synthesize actual speech in a unit test; that belongs in an end-to-end pipeline test, if anywhere.
Test architecture that stays maintainable
The most reliable setup is to isolate your transport code behind small interfaces and inject them into components or hooks. That gives you a stable seam for tests and keeps React Native code from knowing whether messages came from a real socket, a mock socket, or a local simulator.
A practical split looks like this:
A session hook owns connection lifecycle and translates socket/WebRTC events into app state.
The visual component renders that state and emits user actions.
The transport layer is replaceable in tests.
This makes it easy to write focused tests such as:
“When the socket closes, show reconnect UI.”
“When STT emits a final transcript, send it to the agent.”
“When the peer connection receives a remote track, start video playback.”
A few gotchas are worth calling out:
Use fake timers if your code retries, debounces, or times out. Otherwise you will get nondeterministic assertions.
Reset global mocks between tests; WebRTC-ish state leaks easily across test cases.
Avoid over-mocking React state itself. Test the public behavior of the component, not its implementation details.
If you need one real networked test, make it a small integration test and keep everything else mocked.
Where Protoface fits
If your app integrates an actual avatar session, the cleanest place to anchor your tests is the session and signaling boundary. Protoface exposes that boundary through the REST API and the Python/LiveKit integration surfaces, so you can keep your React Native tests focused on deterministic app behavior while still matching the real session model. For implementation details and current field names, use the documentation; for LiveKit-based agent integrations, the relevant plugin examples are in the GitHub org.
A typical pattern in app tests is to mock the session responses and events, not the avatar itself. For example, your UI can pretend it received a ready session and a remote media track, while your slower integration tests can cover the real end-to-end path separately.
That kind of call belongs outside your React Native unit tests. In tests, replace it with a stubbed session object that returns the same shape your app expects.
Conclusion
Mocking WebRTC, WebSocket, and TTS/STT services is mostly about drawing clean boundaries. Keep real network behavior out of React Native unit tests, fake the timing and event sequences your app depends on, and reserve end-to-end coverage for a small number of high-value flows.
If you do that, your avatar tests become fast, stable, and easy to debug. The component test tells you whether the UI responds correctly; the integration test tells you whether the transport stack works. That separation saves a lot of time once realtime media enters the picture.
For the exact session fields, event names, and integration patterns, start with docs.protoface.com.
