Getting Started wtih Marble Tests
Codesandbox
As a reminder, Codesandbox uses Jest by default in the client environment that we are executing our tests within.
Example Test
Let's start to bring this all together and look at a sample test.
import { interval } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
import { filter, map, tap } from 'rxjs/operators';
describe('getting started with RxJS testing with marbles', () => {
let testScheduler: TestScheduler;
beforeEach(() => {
testScheduler = new TestScheduler((actual, expected) =>
expect(actual).toEqual(expected)
);
});
test('say hello world then complete', () => {
testScheduler.run(({ cold, expectObservable }) => {
const values = {
a: 'hello',
b: 'world'
};
const source = cold('-a-b-|', values);
const expected = ' -a-b-|';
expectObservable(source).toBe(expected, values);
});
});
test('filter odd values', () => {
testScheduler.run(({ cold, expectObservable }) => {
const values = {
a: 0,
b: 1,
c: 2,
d: 3,
e: 4,
f: 5
};
const source = cold('abcdef|', values).pipe(filter((value) => value % 2 === 0));
const expected = ' a-c-e-|';
expectObservable(source).toBe(
expected,
values
);
});
});
});
After setting up the TestScheduler
we have two tests.
First, we have a test that mocks an Observable that has to next notifications and then completes.
Second, we have a test that mocks an Observable that filters odd values.
Let's break down the first test:
- We define a
values
object whose property keys are the character that represents the value in the marble string, and the value of the property is the value of the next notification. - We create a new cold Observable using the
cold()
function from theRunHelpers
object, specifying the marble string, and thevalues
. The marble string describes an Observable that emits a next notification value in frames 1 and 3. Charactersa
andb
represent each next notification. The pipe ( | ) character represents a completion notification on frame 5. - We define a new
expected
string that represents the Observable behavior that we expect. To start with, we're not modifying the source observable at all, so we should expect an identical behavior of that which we mocked using thecold()
function. - Finally, we use the
expectObservable()
function to assert thesource
to be deeply equal to theexpected
, and provide thevalues
for the expected Observable.
The second test is a bit more interesting.
In this test we are going to assert that source
Observable filters out odd values.
Let's break down the second test:
- First, we define the
values
object for our next notifications. - We create a new cold Observable using the
cold()
function, specifying the marble string, and thevalues
. The source Observable emits 6 next notification in sequence from frame 0 to frame 5, and then completes on frame 6. Note that on thesource
cold Observable that we are using thefilter()
operator to filter the next notifications. The predicate function provided to thefilter()
operator returnstrue
when the value is even. - The
expect
string describes the behavior after filtering odd values. We should expect to only receive a next notification where the value is even in frames 0, 2, and 4. The Observables should then complete in frame 6. - Finally, we use the
expectObservable()
function to perform the assertion.
Subscriptions
We can also use a marble string to describe the timing of an Observer subscribing using a carrot character ( ^ ). Further, we can indicate the timing of an Observer unsubscribing using the exclamation mark ( ! ).
Here is an example of a hot Observable source, an Observer's subscription, and the expected Observable:
const source = hot(' --a--b--c--d--e--f');
const subscription = '-----^------!';
const expected = ' -----b--c--d|';
In the example above you'll notice that we have an additional subscription
string that describes the Observer's subscribing, and unsubscribing, behaviors.
As indicated, we only expect to receive next notifications while the Observer is subscribed to the source Observable.