使用Jest测试Fetch请求失败,第二个测试未触发预期Action
问题:Jest测试Redux异步Action时未触发contentFetchCompleted
原Fetch请求代码
import fetch from 'cross-fetch'; import { contentFetchCompleted, contentFetchError, contentFetchStarted } from './actions'; export const CONTENT_QUERY = `query SalesContent ($key: String!) { salesContent (key: $key) { data } }`; export const contentFetch = (key, context) => (dispatch, state) => { if (state.content[key] && !state.content[key].fetching && state.content[key].language !== context.culture) { dispatch(contentFetchStarted(key, context.culture)); fetch("/graphql", { method: "POST", headers: { "Content-Type": "application/json", "Accept-Language": context.culture }, body: JSON.stringify({ query: CONTENT_QUERY, variables: { key: key } }) }) .then((resp) => resp.json()) .then((result) => { dispatch(contentFetchCompleted(key, context.culture, result.data.salesContent.data)); }) .catch((err) => { dispatch(contentFetchError(key, context.culture, err)); }); } };
Jest测试代码
const mockFetch = request => { global.fetch = request } const contextMock = { culture: 'nl' }; const dispatchMock = jest.fn(); const stateMock = { content: { [key]: { fetching: false, language: 'fr', }, }, }; beforeAll(() => { mockFetch() }) describe("FETCH CONTENT", () => { beforeEach(() => { // Clear the mock calls before each test dispatchMock.mockClear(); }); test('should dispatch contentFetchStarted action if conditions are met', () => { mockFetch(mockedFetchContentSuccess) // Call the contentFetch function contentFetch(key, contextMock)(dispatchMock, stateMock); // Verify that the dispatch function is called with the expected action expect(dispatchMock).toHaveBeenCalledWith(contentFetchStarted(key, contextMock.culture)); }); test('should fetch data and dispatch contentFetchCompleted action', async () => { // Mock the fetch function to return a response mockFetch(mockedFetchContentSuccess) // Call the contentFetch function await contentFetch(key, contextMock)(dispatchMock, stateMock); // Verify that the dispatch function is called with the expected action await expect(dispatchMock).toHaveBeenCalledWith(contentFetchCompleted(key, contextMock.culture, data)) }); })
报错信息
FETCH CONTENT › should fetch data and dispatch contentFetchCompleted action expect(jest.fn()).toHaveBeenCalledWith(...expected) - Expected + Received Object { "payload": Object { - "data": "Some dummy data", "key": "financing", "language": "nl", }, - "type": "CONTENT_FETCH_COMPLETED", + "type": "CONTENT_FETCH_STARTED", }, Number of calls: 1 45 | 46 | // Verify that the dispatch function is called with the expected action > 47 | await expect(dispatchMock).toHaveBeenCalledWith(contentFetchCompleted(key, contextMock.culture, data)) | ^ 48 | }); 49 | })
更新的mock函数代码
export const mockedFetchContentSuccess = jest.fn(() => { return Promise.resolve({ json: () => Promise.resolve({ data: { salesContent: { data: data }, }, }), }) } );
解决思路
1. 让异步Action返回Promise
原contentFetch返回的函数没有返回fetch的Promise链,导致await无法等待异步流程完成,测试会在contentFetchStarted触发后直接结束,不会等到contentFetchCompleted的调用。修改代码:
export const contentFetch = (key, context) => (dispatch, state) => { if (state.content[key] && !state.content[key].fetching && state.content[key].language !== context.culture) { dispatch(contentFetchStarted(key, context.culture)); // 返回fetch的Promise链,让await能捕获异步流程 return fetch("/graphql", { method: "POST", headers: { "Content-Type": "application/json", "Accept-Language": context.culture }, body: JSON.stringify({ query: CONTENT_QUERY, variables: { key: key } }) }) .then((resp) => resp.json()) .then((result) => { dispatch(contentFetchCompleted(key, context.culture, result.data.salesContent.data)); }) .catch((err) => { dispatch(contentFetchError(key, context.culture, err)); }); } // 条件不满足时返回空Promise,避免await抛出错误 return Promise.resolve(); };
2. 修正测试中的断言逻辑
第二个测试用例中,dispatch会被调用两次:一次触发contentFetchStarted,一次触发contentFetchCompleted。原断言只检查了后者,但因为异步流程没走完,导致只调用了一次。修改测试验证所有调用:
test('should fetch data and dispatch contentFetchCompleted action', async () => { mockFetch(mockedFetchContentSuccess) await contentFetch(key, contextMock)(dispatchMock, stateMock); // 验证dispatch被调用两次 expect(dispatchMock).toHaveBeenCalledTimes(2); // 验证第一次调用是STARTED动作 expect(dispatchMock).toHaveBeenNthCalledWith(1, contentFetchStarted(key, contextMock.culture)); // 验证第二次调用是COMPLETED动作 expect(dispatchMock).toHaveBeenNthCalledWith(2, contentFetchCompleted(key, contextMock.culture, data)); });
3. 检查mock变量的定义
确保mockedFetchContentSuccess中引用的data变量在测试文件中已正确定义,避免因变量未定义导致返回的data为undefined,影响后续action的触发逻辑。
内容的提问来源于stack exchange,提问作者Boky
相关产品推荐
相关产品推荐

