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

Jest mock方法首次调用返回预期值,二次调用返回空数组的问题求助

Jest mock方法首次调用返回预期值,二次调用返回空数组的问题求助

我正在开发一个用Jest测试的React应用,目前整体进展顺利。我有一个依赖洗牌服务的组件,组件靠这个服务来渲染,所以我在beforeEach里mock了这个服务,在afterEach里重置mock。到目前为止,所有测试都运行正常。

import '@testing-library/jest-dom';

import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';

import { Card, Suit } from '../cardContent/cardContent';
import { BuildDeckService } from '../services/buildDeck/buildDeck.service';
import GameLogic from './gameLogic';

describe('GameLogic', () => {
  beforeEach(() => {
    const expectedDeck: Card[] = [ ... ];
    jest.spyOn(BuildDeckService, 'buildDeck').mockReturnValue(expectedDeck);
  });
  afterEach(() => {
    jest.resetAllMocks();
  });
  // 我的所有测试
});

不过最近我写了几个测试,需要buildDeck函数被调用两次——一次是组件首次渲染时,另一次是开始新游戏时。

it('reshuffles and deals the deck', async () => {
  const user = userEvent.setup();
  render(<GameLogic></GameLogic>); // 第一次调用buildDeck

  const tableauSection = screen.getByTestId('tableau');
  const tableauPiles = within(tableauSection).getAllByRole('button');

  const foundationSection = screen.getByTestId('foundations');
  const foundations = within(foundationSection).getAllByRole('button');

  await user.click(tableauPiles[12]);
  await user.click(foundations[0]);

  const newGameBtn = screen.getByRole('button', { name: 'New Game' });

  await user.click(newGameBtn); // 第二次调用buildDeck

  expect(within(tableauPiles[12]).getByText('A')).toBeVisible();
  expect(within(tableauPiles[12]).getByAltText('of Spades')).toBeVisible();
});

供参考,await user.click(newGameBtn);只是调用了一个叫startNewGame()的函数:

const startNewGame = () => {
  setFoundations([[], [], [], [], [], [], [], []]);
  setTableau(dealNewGame());
};
const dealNewGame = (): Card[][] => {
  const deck: Card[] = BuildDeckService.buildDeck();
  // 我可以在这里console.log(deck),看到第一次mock调用时牌组是完整的,第二次是空数组
  const newTableau: Card[][] = [[], [], [], [], [], [], [], [], [], [], [], [], [], []];
  // 大量将牌组发牌到newTableau的逻辑
  return newTableau;
};

这些测试失败了,因为第二次调用buildDeck时,它返回的是空数组[]而不是expectedDeck,导致组件后续逻辑混乱。这和我对mockReturnValue()工作方式的理解不符,也让我没法完成新功能:(

为了解决这个问题,我试了能想到的最hack的办法:在测试里,就在用户点击新游戏按钮之前,重置所有mock并重新定义返回值。后来我简化成只重新定义mock,测试变成了这样:

it('reshuffles and deals the deck', async () => {
  const user = userEvent.setup();
  render(<GameLogic></GameLogic>);

  const tableauSection = screen.getByTestId('tableau');
  const tableauPiles = within(tableauSection).getAllByRole('button');

  const foundationSection = screen.getByTestId('foundations');
  const foundations = within(foundationSection).getAllByRole('button');

  await user.click(tableauPiles[12]);
  await user.click(foundations[0]);

  const newGameBtn = screen.getByRole('button', { name: 'New Game' });
  jest.spyOn(BuildDeckService, 'buildDeck').mockReturnValue(expectedDeck);

  await user.click(newGameBtn);

  expect(within(tableauPiles[12]).getByText('A')).toBeVisible();
  expect(within(tableauPiles[12]).getByAltText('of Spades')).toBeVisible();
});

这确实让测试通过了,证明主代码是没问题的,测试写法也正确。但这需要在beforeEach块内外都定义expectedDeck,不然会破坏原来的测试。

而且只有单独运行这个测试时才有效。目前有两个测试需要调用两次mock服务,不管顺序如何,第二个测试总是失败,即使我在afterEach里调用了jest.resetAllMocks()。

从那以后,我尝试了各种定义mock的方式,包括BuildDeckService.buildDeck = jest.fn().mockReturnValue(expectedDeck)、jest.spyOn(BuildDeckService, 'buildDeck').mockImplementation(() => expectedDeck);、(BuildDeckService.buildDeck as jest.Mock).mockReturnValue(expectedDeck),甚至在文件顶部用jest.mock('.../filePathToBuildDeckService'),但都没效果。

我还在afterEach块里试了所有版本的jest.resetAllMocks()、jest.clearAllMocks()、jest.restoreAllMocks、(BuildDeckService.buildDeck as jest.Mock).mockReset()等等——都没用。

我试过把mock定义和重置放在beforeAll()和afterAll()块里——没变化。

我试过链式调用两次.mockReturnValueOnce(expectedDeck)——没变化。

我问过Copilot(它给我展示了一堆花里胡哨但没用的mock方式)、各种Discord服务器上的人,也查了文档,但都没进展。

感觉这肯定是某个简单又基础的点,容易被忽略。而且看起来mockReturnValue()的工作方式和我想的不一样,我以为只要在最外层的beforeEach()里设置一次,不管调用多少次mock服务,都会返回同样的结果(假设测试之间重置了mock,我觉得我已经这么做了)。

也不是说第二次调用时mock失效了——如果是那样的话,我应该得到随机洗牌的牌组,而不是空数组。问题很明确:第二次调用mock时,它返回的是空数组,而不是我指定的内容。我甚至都没尝试改变第二次调用的返回值!

到底是怎么回事?为什么调用同一个mock服务两次时测试会失败?

备注:内容来源于stack exchange,提问作者Surfdaweb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 15:09:56