In an RxJS marble test, when do you create a source with hot() instead of cold(), and what does ^ mark?
answer
- fresh timeline per subscriber
- one shared timeline
- the caret is the zero frame
- events before it are negative
basics
~20 sIn RxJS marble tests, cold() replays its timeline from each subscription, so it suits request stubs; hot() runs one shared timeline, so it suits user input or subjects. In a hot() diagram, ^ marks frame zero, where the tested code subscribes.
solid answer
~50 s`cold(marbles)` creates an Observable whose diagram starts at the moment **each** subscriber subscribes: subscribe at frame 10 and the diagram's frame 0 happens at frame 10. That models a request or any lazily started work, and it is what you return from a stub that `switchMap` or `mergeMap` will call. `hot(marbles)` creates an Observable whose events happen on fixed frames of the test's timeline regardless of who is subscribed, like a `Subject`, a DOM event source or a websocket. A late subscriber simply misses earlier events. In a `hot()` diagram, `^` marks the **zero frame**, the point where the observable under test is subscribed; anything to its left happens at negative frames, which is how you model values that were emitted before the test subscribed. `^` is not allowed in `cold()` diagrams. Both helpers record subscriptions in a `subscriptions` property for `expectSubscriptions`.
go deeper
Recall that cold() restarts for each subscriber and hot() runs on a shared timeline, with ^ marking where the test subscribes.
Explain negative frames left of ^, why request stubs are cold and user input is hot, and what each helper's subscriptions log records.
Pick fixtures that expose real bugs, such as hot input shared by two consumers, and use negative frames to test replaying streams.
Encourage fixtures that mirror production temperature, so tests catch multicasting and timing bugs instead of passing by construction.
## Two kinds of fixture The `hot()` and `cold()` helpers that `TestScheduler.run` provides both parse a marble string into an Observable, but they differ in **whose clock** the diagram is drawn against. | | `cold(marbles)` | `hot(marbles)` | |---|---|---| | Timeline starts | when each subscriber subscribes | at the test's zero frame, for everyone | | Two subscribers at different times | each sees the whole diagram, shifted | both see the same events; the later one misses earlier ones | | `^` allowed | no, it throws | yes, marks the zero frame | | Models | a request, a lazily started computation, an inner observable | user input, a `Subject`, DOM events, a socket | | `subscriptions` log | yes | yes | ## cold(): one private timeline per subscription A cold fixture replays its diagram from the frame a subscriber arrives. If `const req = cold('--r|')` is subscribed on frame 100, `r` arrives on frame 102 and completion on frame 103. Subscribe again on frame 200, and the second subscriber gets its own `r` on frame 202. That makes `cold()` the right stub for anything returned from a function that a flattening operator calls: each call is a new request whose latency is measured from the moment it starts. ## hot(): one shared timeline A hot fixture behaves like a `Subject` that some producer drives on a fixed schedule. Events are placed on the test's own frames, whether or not anything is subscribed. A subscriber that arrives on frame 5 does not see a value scheduled for frame 3. This matches keystrokes, clicks and server pushes: the user does not start typing because a stream subscribed. ## The ^ subscription point In a `hot()` diagram, `^` marks **frame zero**, the frame at which the code under test is subscribed by `expectObservable`. Characters to the left of it are at **negative frames**: ```ts testScheduler.run(({ hot, expectObservable }) => { const source = hot('-a-^-b--|'); expectObservable(source).toBe('--b--|'); }); ``` - `a` is emitted at frame -2, before the subscription, so the tested subscriber never sees it. - `b` is emitted at frame 2 and completion at frame 5. Negative time matters when the thing under test replays history. If a replaying multicast operator or a `ReplaySubject` was already connected to the fixture before frame zero, it can hand the value from frame -2 to a subscriber arriving at frame 0, and `^` is how the test puts that value in the past. ## Several subscribers to one hot fixture With a hot fixture you can check what subscribers joining at different times receive by giving `expectObservable` a subscription marble as its second argument: ```ts testScheduler.run(({ hot, expectObservable }) => { const source = hot('--a--a--a--a--a--a--a--'); const sub1 = ' --^-----------!'; const sub2 = ' ---------^--------!'; expectObservable(source, sub1).toBe('--a--a--a--a--'); expectObservable(source, sub2).toBe('-----------a--a--a-'); }); ``` The first subscriber joins on frame 2 and leaves on frame 14; the second joins on frame 9 and so misses the values that were emitted before it arrived. The same test written with `cold()` would give each subscriber its own full run of the diagram. ## Choosing in practice - **Inputs that exist independently of the subscriber**: `hot()`. Search terms, router events, websocket messages, a `BehaviorSubject`'s later updates. - **Work that starts because of a subscription**: `cold()`. HTTP-like calls, `defer` factories, inner observables. - **Checking late subscribers or shared streams**: `hot()` together with `expectObservable(source$, subscriptionMarbles)`, so different subscribers can join at different frames. - **Asserting when work was started or cancelled**: either helper's `subscriptions` property with `expectSubscriptions`. ## Common mistakes 1. Using `hot()` for a request stub, so its response frame is fixed instead of relative to when the request started; the test passes by coincidence and breaks when timing changes. 2. Using `cold()` for user input shared by two consumers, so each consumer gets its own copy of the keystrokes and a multicasting bug stays hidden. 3. Writing `^` in a `cold()` diagram, which `TestScheduler` rejects with an error because a cold fixture has no shared zero frame. 4. Forgetting that a value left of `^` is in the past: expecting it in the output of a non-replaying stream.
- When would a value to the left of ^ ever reach the tested subscriber?Only if the code under test replays history, for example a stream built on a `ReplaySubject` or a replaying share operator that subscribed to the hot fixture before frame zero. A plain subscriber to `hot('-a-^-b|')` sees only `b` and the completion.
- Can a cold() fixture be subscribed more than once in one test, and how do you check it?Yes. Each subscription gets its own run of the diagram, and every one is recorded in the fixture's `subscriptions` array. Pass that array to `expectSubscriptions` with one subscription marble per expected subscription to assert when each started and ended.
cold() is a recorded lecture that starts from the beginning whenever a student presses play; hot() is a live lecture, and ^ marks the moment the student walks in, so anything said before is missed.
saying these in an interview costs you the question
- hot() and cold() differ only in which helper name you prefer.
- A value to the left of ^ is delivered to every subscriber.
- Request stubs returned to switchMap should be hot() fixtures.
- The ^ character can mark a subscription point in cold() diagrams too.
- A late subscriber to a hot() fixture replays the events it missed.