You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 23:25:05