reactjs - How to test api calls within redux-saga
问题描述
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
解决方案
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
推荐阅读
- javascript - 由于标签“文件:”,脚本无法加载
- kotlin - 我什么时候应该让我的正常功能暂停功能?
- python-3.x - TypeError:输入类型不支持 ufunc 'isnan'
- group-by - 扩展列表以包括 SAS 中的因子变换
- r - flexboard 闪亮的桌子水平和垂直滚动条不起作用
- check-constraints - SQL Server:检查约束表达式
- firebase - 使用 StreamBuilder 从 Firebase(实时数据库)Flutter 获取数据
- java - 如何使用 Talend Open Studio 或在 java 中冻结 .xslx 的标头
- testing - 浏览器堆栈上的文本日志中的 Testcafe “章节”
- apache-flink - 如何在 Apache Flink 作业中获取 System.getProperties()