Reputation: 836
I have this saga effects that calls to an API and dispatched an action if it is successful:
export function* getThemEffect() {
try {
yield put(requestActoin());
const data: AxiosResponse<ServerResponseSchema> = yield call(getStuff);
yield put(successAction(data.data.data));
} catch (err: any) {
yield put(failureAction(err?.response?.data || null));
}
}
This is the helper function:
export function getStuff() {
const config: AxiosRequestConfig = {
method: "GET",
url: "https://somewhere.com/api/get"
};
return axios(config);
}
The test suit for this saga looks like this:
import * as api from "../api";
const getStuffSpy = jest.spyOn(api, "getStuff");
describe("search saga", () => {
let gen: Generator, response: any, getStuffMock: jest.Mock;
beforeEach(() => {
getStuffSpy.mockClear();
gen = getThemEffect();
getStuffMock = jest.fn();
getStuffSpy.mockImplementation(getStuffMock);
});
describe("server success response", () => {
beforeEach(() => {
response = { data: { data: ["1", "2", "3"] } };
});
it("should create correct success flow", () => {
expect(gen.next()).toEqual({
value: put(requestAction()),
done: false
});
expect(gen.next()).toEqual({
value: call(api.getStuff),
done: false
});
expect(getStuffMock).toHaveBeenCalled(); // <=== this fails
expect(gen.next(response)).toEqual({
value: put(successAction(["1", "2", "3"])),
done: false
});
expect(gen.next()).toEqual({
value: undefined,
done: true
});
});
});
}
However the test that expects the getStuffMock
function to have been called fails. How can I fix this? I am using jest with testing-library
Upvotes: 4
Views: 3291
Reputation: 102237
call(fn, ...args)
is just a function that returns a plain Effect Object. It will not execute the fn
call immediately. When testing the saga generator function step-by-step, you manually execute the generator and provide the value of yield
by .next ()
method, the getStuff
function will not execute.
The call(getStuff)
just returns a Effect object like this:
{
CALL: {
fn: getStuff,
}
}
If you want to execute the mocked getStuff
function, you need to test the saga in this way - Testing the full Saga.
runSaga
will get the Effect object and execute the function it holds.
Test example:
saga.ts
:
import { call, put } from 'redux-saga/effects';
import { getStuff } from './api';
export const requestAction = () => ({ type: 'REQUEST' });
export const successAction = (data) => ({ type: 'SUCCESS', payload: data });
export const failureAction = (error) => ({ type: 'FAILURE', payload: error, error: true });
export function* getThemEffect() {
try {
yield put(requestAction());
const data = yield call(getStuff);
yield put(successAction(data.data.data));
} catch (err: any) {
yield put(failureAction(err?.response?.data || null));
}
}
api.ts
:
import axios, { AxiosRequestConfig } from 'axios';
export function getStuff() {
const config: AxiosRequestConfig = {
method: 'GET',
url: 'https://somewhere.com/api/get',
};
return axios(config);
}
saga.test.ts
:
import { runSaga } from '@redux-saga/core';
import { call, put } from '@redux-saga/core/effects';
import { mocked } from 'ts-jest/utils';
import { getStuff } from './api';
import { getThemEffect, requestAction, successAction } from './saga';
jest.mock('./api');
const getStuffMock = mocked(getStuff);
describe('search saga', () => {
it('should create correct success flow', () => {
const gen = getThemEffect();
const response = { data: { data: ['1', '2', '3'] } };
expect(gen.next()).toEqual({
value: put(requestAction()),
done: false,
});
expect(gen.next()).toEqual({
value: call(getStuff),
done: false,
});
expect(gen.next(response)).toEqual({
value: put(successAction(['1', '2', '3'])),
done: false,
});
expect(gen.next()).toEqual({
value: undefined,
done: true,
});
});
it('should pass', async () => {
const response = { data: { data: ['1', '2', '3'] } };
const dispatched: any[] = [];
getStuffMock.mockResolvedValueOnce(response as any);
await runSaga(
{
dispatch: (action) => dispatched.push(action),
getState: () => ({}),
},
getThemEffect,
).toPromise();
expect(dispatched).toEqual([{ type: 'REQUEST' }, { type: 'SUCCESS', payload: ['1', '2', '3'] }]);
expect(getStuffMock).toHaveBeenCalled();
});
});
test result:
PASS redux-saga-examples packages/redux-saga-examples/src/stackoverflow/69371886/saga.test.ts
search saga
✓ should create correct success flow (4 ms)
✓ should pass (3 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 80.95 | 0 | 60 | 78.57 |
api.ts | 50 | 100 | 0 | 50 | 4-8
saga.ts | 88.24 | 0 | 75 | 90 | 14
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 4.662 s
Upvotes: 9