Let’s walk through a scenario together:

A user is presented with a list of Github users in a web application. By clicking on a list item, we will fetch the respective Github user’s public repositories and display.

When selecting a Github user, we dispatch a UPDATE_SELECTED_USER action to update selectedUser in the store. After updating, we want to dispatch a fetchRepos action to signal fetching from the Github. Once receiving API response, the store will be updated with either successful response or error. The action flow looks like this:

Action flow

Testing Observables that Perform Fetch

We want to start with writing a service to request the repository list. We are saving the response data or updating error status based on whether the request is successful.

For our first test, we want to see if a successful request immediately triggers fetchReposSuccessful action in the service. The marble diagram and the value of the marbles looks like this:

const marbles = {

i: '-i', // api response

o: '-o', // output action

}; const values = {

i: response,

o: fetchReposSuccessful(response),

};

Notice that we are listing the marble diagrams together. I find it to be a very clean representation to show emitted values over time. We can add descriptions or intermediate steps in this structure to improve readability:

const marbles = {

i: '-i', // api response

// == tap ==

// '-i'

// == map ==

o: '-o', // output action

};

Next, just like in the official doc, we will enclose our test in TestScheduler.run .

scheduler.run(({ cold, expectObservable }) => {

const getJSON = (url: string) => cold(marbles.i, values);

const output$ = fetchGithubRepos('test-user', getJSON);

expectObservable(output$).toBe(marbles.o, values);

});

Done✓ Now we test if the observable emits fetchReposFailed when API request fails. The tricky part of the test is to mock failed request. What we can do is to create an observable with timer .

The cool thing is that we can create time span for timer with TestScheduler.

const duration = scheduler.createTime('-|');

const getJSON = (url: string) =>

timer(duration).pipe(mergeMap(() => throwError(error)));

Instead of having duration time in millisecond, we create timer with a mock duration in marble notation and TestSchedule . So when we execute getJSON , it will emit an error after one tick in the scheduler. The expected output looks like this:

const marbles = {

d: '-|', // mock api response time duration

o: '-(o|)', // output action. Complete when error thrown.

}; const values = {

o: fetchReposFailed(error),

}; scheduler.run(({ expectObservable }) => {

const output$ = fetchGithubRepos('test-user', getJSON as any);

expectObservable(output$).toBe(marbles.o, values);

});

You can find the full test below.

Testing Epics without state$

We are ready to write the first epic that uses the fetchGithubRepos service to fetch repositories.

It’s a straight forward epic. We want to listen to FETCH_REPOS_REQUESTED action type, pick the username from the action payload, and execute the service observable with the username. Notice that we are using switchMap so we are able to cancel duplicated API request.

To test the epic, we want to see if it listens to the right action and if it only emit the most recent action observable. The marbles looks like this:

const marbles = {

r: '--r', // mock api response

i: '-ii--i', // input action

// == switchMap() ==

// only emit the latest value for consecutive inputs

// '----r--r'

// == map() ==

o: '----o--o', // output action

}; const values = {

i: fetchRepos('test-user'),

r: response,

o: fetchReposSuccessful(response),

};

Now construct the test.

scheduler.run(({ hot, cold, expectObservable }) => {

const action$ = hot(marbles.i, values) as any;

const state$ = null as any;

const dependencies = {

getJSON: (url: string) => cold(marbles.r, values),

};

const output$ =

fetchGithubReposEpic(action$, state$, dependencies);

expectObservable(output$).toBe(marbles.o, values);

});

To simulate input action observables, we use hot helper from the callback function of scheduler.run to create hot observables. Action observables are cold in nature; However when they go through redux-observable ’s middleware, the action observables are made hot by setting Subjects as observers. Read more about cold and hot observables in Ben Lesh’s article. In the article, he also touched on how to make code observable hot.

You can see that we are asserting action$ as any for fetchGithubReposEpic to use. That’s because an Epic is expecting action$ to be ActionsObservable type instead of ColdObservable type. CodeObservable extends Observable and it’s a helper observable in TestScheduler to facilitate testing. We can safely assert.

We assigned state$ to null because we are not accessing the store in the epic.

For dependencies, we mock getJSON with cold helper to simulate successful API request.

Testing Epic with state$

Now we can complete the last piece to complete the full action flow.

We are creating an epic that listens to updateSelectedUser action and use the just updated username to trigger fetchGithubRepos service observable.

To test it we need to know how to mock state observables. State observables ( state$ ) are actually create by StateObservable . StateObservable takes in a Subject and a Redux state as parameters.

const reduxState: AppState = {

selectedUser: 'test-user',

githubRepos: reposInitialState,

}; const state$ = new StateObservable<AppState>(

new Subject(), reduxState);

We are ready to assemble the test together:

Now let’s see the test result!

Let’s try it out if it works too.

Feel free to take a look at the demo I put together. You can find more details about how to type epics and observables there.