jest:无法使用 `jest.spyOn` 模拟方法/钩子

huangapple go评论74阅读模式
英文:

jest: unable to mock methods/hooks using `jest.spyOn`

问题

I'll provide the translation of the relevant code parts you provided:

我正在尝试模拟文件中的方法和钩子并根据需要导入这些模拟函数到我的测试文件中

`useMyHook.jsx`
```javascript
const useMyHook = () => {
  const [val, setVal] = useState(200)

  return { val }
}

export { useMyHook }

Hello.jsx:

import { useTestHook } from "@/hooks/useTestHook";

export const Hello = () => {
  const { val } = useTestHook();
  console.log({ val });

  return (
    <div>
      <h2>Hello</h2>
    </div>
  );
};

这是我模拟钩子和函数的文件。
mock-hooks.js:

import { useMyHook } from "@/hooks/useMyHook";
// import * as Hook from "@/hooks/useMyHook";

const mocks = {
  useMyHook: (val = 9999) => {
    jest
      .spyOn({ useMyHook }, "useMyHook")
      .mockImplementation(() => ({ val }));

    /** 这种模拟的方法以前有效,但现在会出现“TypeError: 无法重新定义属性: useMyHook”错误 */
    // jest.spyOn(Hook, "useMyHook")
    //   .mockImplementation(() => ({ val }));
  },
};

export { mocks };

useMyHook 钩子是一个最简单的钩子,只返回一个值(状态)。

我在我的测试文件中导入 mocks 变量并调用 mocks.useMyHook(),但它不起作用。在测试时,我仍然得到原始钩子的值。

我正在测试一个使用 useMyHook 钩子的 Hello.jsx 组件,该组件在控制台中记录其返回值。

Hello.test.jsx:

import { mocks } from "@/utils/test/mock-hooks";

import { Hello } from "./Hello";
import { render } from "@testing-library/react";

describe("Hello 测试", () => {
  beforeEach(() => {
    mocks.useMyHook(12345);
    render(<Hello />, {
      wrapper: ({ children }) => <>{children}</>,
    });
  });

  test("应该渲染主容器", () => {
    screen.debug()
  });
});

当我运行测试时,我仍然看到原始钩子的值被记录在控制台中,而不是模拟值(12345)。

我发现在 describe 之前使用 jest.mock 而不是 jest.spyOn 能够解决问题。但我有很多测试,在测试过程中会改变模拟的值(例如,在特定的 test() 块内),通过调用 mocks.useMyHook 钩子会使该钩子返回另一个值以用于特定的测试用例。以前使用的 spyOn 方法能够正常工作,但是项目中发生了很多变化,所以我无法使其工作。

我在一个 Next.js 项目上工作。以下是项目的一些信息:

依赖项:

{
  ...
 "dependencies": {
    "next": "12.3.0",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "@babel/core": "7.17.8",
    "@testing-library/jest-dom": "5.16.4",
    "@testing-library/react": "12.1.2",
    "@testing-library/react-hooks": "7.0.2",
    "babel-jest": "27.4.6",
    "jest": "28.1.0",
    "jest-environment-jsdom": "28.1.0"
  }
}

jest.config.js:

const nextJest = require("next/jest");

const createJestConfig = nextJest({});

const customJestConfig = {
  collectCoverageFrom: ["**/*.{js,jsx,ts,tsx}"],
  verbose: true,
  modulePaths: ["<rootDir>/"],
  modulePathIgnorePatterns: ["<rootDir>/.next"],
  testPathIgnorePatterns: ["<rootDir>/node_modules/", "<rootDir>/.next/"],
  transform: {
    "^.+\\.(js|jsx|ts|tsx)$": ["babel-jest", { presets: ["next/babel"] }],
  },
  transformIgnorePatterns: [
    "/node_modules/",
    "^.+\\.module\\.(css|sass|scss)$",
  ],
  testEnvironment: "jsdom",
  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/$1",
  },
};

module.exports = createJestConfig(customJestConfig);

.babelrc:

{
  "presets": ["next/babel"],
  "plugins": []
}

编辑:修正了拼写错误并添加了 useMyHook 钩子和 Hello 组件的代码。


<details>
<summary>英文:</summary>

I&#39;m trying to mock the methods &amp; hooks in a file and import those mock functions as I need them, in my test files.

`useMyHook.jsx`
```javascript
const useMyHook = () =&gt; {
  const [val, setVal] = useState(200)

  return { val }
}

export { useMyHook }

Hello.jsx:

import { useTestHook } from &quot;@/hooks/useTestHook&quot;;

export const Hello = () =&gt; {
  const { val } = useTestHook();
  console.log({ val });

  return (
    &lt;div&gt;
      &lt;h2&gt;Hello&lt;/h2&gt;
    &lt;/div&gt;
  );
};

this is the file where i mock the hooks & functions.
mock-hooks.js:

import { useMyHook } from &quot;@/hooks/useMyHook&quot;;
// import * as Hook from &quot;@/hooks/useMyHook&quot;;

const mocks = {
  useMyHook: (val = 9999) =&gt; {
    jest
      .spyOn({ useMyHook }, &quot;useMyHook&quot;)
      .mockImplementation(() =&gt; ({ val }));

    /** this method of mocking used to work before, but now it gives 
        `TypeError: Cannot redefine property: useMyHook` */
    // jest.spyOn(Hook, &quot;useMyHook&quot;)
    //   .mockImplementation(() =&gt; ({ val }));

  },
};

export { mocks };

the useMyHook hook is a bare minimum hook which just returns a value(state).

i'm importing the mocks variable in my test file and calling mocks.useMyHook(), but it doesn't work. I still get the original hook's value in return while testing.

I'm testing a Hello.jsx component which uses useMyHook hooks and logs its return value in console.

Hello.test.jsx:

import { mocks } from &quot;@/utils/test/mock-hooks&quot;;

import { Hello } from &quot;./Hello&quot;;
import { render } from &quot;@testing-library/react&quot;;

describe(&quot;Hello test&quot;, () =&gt; {
  beforeEach(() =&gt; {
    mocks.useMyHook(12345);
    render(&lt;Hello /&gt;, {
      wrapper: ({ children }) =&gt; &lt;&gt;{children}&lt;/&gt;,
    });
  });

  test(&quot;should render the main container&quot;, () =&gt; {
    screen.debug()
  });
});

when i run the test, I still see the original hook's value being logged in console, instead of the mock value(12345).

I found out that using jest.mock before the describe instead of jest.spyOn works.
But I have a lot of tests that change the mocked value during the tests(like inside a specific test() block) by calling mocks.useMyHook and the hook would return another value for that specific test cases.
The spyOn method used to work previously fine, but alot of things changed in the project so now I'm unable to make it work.

I'm on a next js project.
Here's some info on the project:

dependencies:

{
  ...
 &quot;dependencies&quot;: {
    &quot;next&quot;: &quot;12.3.0&quot;,
    &quot;react&quot;: &quot;17.0.2&quot;,
    &quot;react-dom&quot;: &quot;17.0.2&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@babel/core&quot;: &quot;7.17.8&quot;,
    &quot;@testing-library/jest-dom&quot;: &quot;5.16.4&quot;,
    &quot;@testing-library/react&quot;: &quot;12.1.2&quot;,
    &quot;@testing-library/react-hooks&quot;: &quot;7.0.2&quot;,
    &quot;babel-jest&quot;: &quot;27.4.6&quot;,
    &quot;jest&quot;: &quot;28.1.0&quot;,
    &quot;jest-environment-jsdom&quot;: &quot;28.1.0&quot;
  }
}

jest.config.js:

const nextJest = require(&quot;next/jest&quot;);

const createJestConfig = nextJest({});

const customJestConfig = {
  collectCoverageFrom: [&quot;**/*.{js,jsx,ts,tsx}&quot;],
  verbose: true,
  modulePaths: [&quot;&lt;rootDir&gt;/&quot;],
  modulePathIgnorePatterns: [&quot;&lt;rootDir&gt;/.next&quot;],
  testPathIgnorePatterns: [&quot;&lt;rootDir&gt;/node_modules/&quot;, &quot;&lt;rootDir&gt;/.next/&quot;],
  transform: {
    &quot;^.+\\.(js|jsx|ts|tsx)$&quot;: [&quot;babel-jest&quot;, { presets: [&quot;next/babel&quot;] }],
  },
  transformIgnorePatterns: [
    &quot;/node_modules/&quot;,
    &quot;^.+\\.module\\.(css|sass|scss)$&quot;,
  ],
  testEnvironment: &quot;jsdom&quot;,
  moduleNameMapper: {
    &quot;^@/(.*)$&quot;: &quot;&lt;rootDir&gt;/$1&quot;,
  },
};

module.exports = createJestConfig(customJestConfig);

.babelrc:

{
  &quot;presets&quot;: [&quot;next/babel&quot;],
  &quot;plugins&quot;: []
}

EDIT: Typo & Added useMyHook hook & Hello component code.

答案1

得分: 0

只更新jest.config.js中的transform属性解决了这个问题。

transform: {
    '^.+\\.(js|jsx|ts|tsx|mjs)$': ['babel-jest', { presets: ['next/babel'] }]
},

我也需要添加mjs到转换模式中。

希望对某人有所帮助。

英文:

just updating the jest.config.js's transform property solved the issue.

transform: {
    &#39;^.+\\.(js|jsx|ts|tsx|mjs)$&#39;: [&#39;babel-jest&#39;, { presets: [&#39;next/babel&#39;] }]
  },

i needed to add mjs as well, to the transform patterns.

Hope this helps someone.

huangapple
  • 本文由 发表于 2023年5月10日 19:27:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76217844.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定