笑话:如何正确模拟节点模块?

Posted

技术标签:

【中文标题】笑话:如何正确模拟节点模块?【英文标题】:Jest: How to correctly mock a node module? 【发布时间】:2018-12-31 17:26:38 【问题描述】:

我想用 Jest 在 React Native 中模拟 node_module 'React Native Keychain'。

在docs 之后,我创建了一个名为__mocks__ 的文件夹,并在其中创建了一个名为react-native-keychain.js 的文件。

这是文件中的代码:

export default jest.mock("react-native-keychain", () => 
  const token = "abcdefghijklmnopqrstuvwxyz0123456789";
  const credentials = 
    username: "session",
    password: token
  ;
  return 
    setGenericPassword: jest.fn(
      (username, password) => new Promise((resolve, reject) => resolve(true)) // eslint-disable-line no-unused-vars
    ),
    getGenericPassword: jest.fn(() => new Promise((resolve, reject) => resolve(credentials))), // eslint-disable-line no-unused-vars
    resetGenericPassword: jest.fn(() => new Promise((resolve, reject) => resolve(true))) // eslint-disable-line no-unused-vars
  ;
);

然后我为使用这个库的函数编写了测试:

import * as keyChainFunctions from "react-native-keychain";
import  setToken, getToken, clearToken  from "./secureStorage";

const token = "abcdefghijklmnopqrstuvwxyz0123456789";

    describe("set token", () => 
      it("saves the token in the keychain", () => 
        expect.assertions(1);
        return setToken(token).then(res => 
          console.log(res);
          console.log(keyChainFunctions);
          expect(keyChainFunctions.setGenericPassword).toHaveBeenCalledWith("session", token);
        );
      );
    );

问题是,测试失败了。我收到错误消息:

 FAIL  app/api/secureStorage/secureStorage.test.js
  set token
    ✕ saves the token in the keychain (42ms)

  ● set token › saves the token in the keychain

    expect(jest.fn())[.not].toHaveBeenCalledWith()

    jest.fn() value must be a mock function or spy.
    Received: undefined

      10 |       console.log(res);
      11 |       console.log(keyChainFunctions);
    > 12 |       expect(keyChainFunctions.setGenericPassword).toHaveBeenCalledWith("session", token);
         |                                                    ^
      13 |     );
      14 |   );
      15 | );

      at app/api/secureStorage/secureStorage.test.js:12:52
      at tryCallOne (node_modules/promise/lib/core.js:37:12)
      at node_modules/promise/lib/core.js:123:15
      at flush (node_modules/asap/raw.js:50:29)
 ● set token › saves the token in the keychain

    expect.assertions(1)

    Expected one assertion to be called but received zero assertion calls.

       6 | describe("set token", () => 
       7 |   it("saves the token in the keychain", () => 
    >  8 |     expect.assertions(1);
         |            ^
       9 |     return setToken(token).then(res => 
      10 |       console.log(res);
      11 |       console.log(keyChainFunctions);

      at Object.<anonymous> (app/api/secureStorage/secureStorage.test.js:8:12)

还有console.log() 产量:

console.log app/api/secureStorage/secureStorage.test.js:10
    true

  console.log app/api/secureStorage/secureStorage.test.js:11
     default:
        addMatchers: [Function: addMatchers],

         advanceTimersByTime: [Function: advanceTimersByTime],
         autoMockOff: [Function: disableAutomock],
         autoMockOn: [Function: enableAutomock],
         clearAllMocks: [Function: clearAllMocks],
         clearAllTimers: [Function: clearAllTimers],
         deepUnmock: [Function: deepUnmock],
         disableAutomock: [Function: disableAutomock],
         doMock: [Function: mock],
         dontMock: [Function: unmock],
         enableAutomock: [Function: enableAutomock],
         fn: [Function: bound fn],
         genMockFromModule: [Function: genMockFromModule],
         isMockFunction: [Function: isMockFunction],
         mock: [Function: mock],
         requireActual: [Function: bound requireModule],
         requireMock: [Function: bound requireMock],
         resetAllMocks: [Function: resetAllMocks],
         resetModuleRegistry: [Function: resetModules],
         resetModules: [Function: resetModules],
         restoreAllMocks: [Function: restoreAllMocks],
         retryTimes: [Function: retryTimes],
         runAllImmediates: [Function: runAllImmediates],
         runAllTicks: [Function: runAllTicks],
         runAllTimers: [Function: runAllTimers],
         runOnlyPendingTimers: [Function: runOnlyPendingTimers],
         runTimersToTime: [Function: runTimersToTime],
         setMock: [Function: setMock],
         setTimeout: [Function: setTimeout],
         spyOn: [Function: bound spyOn],
         unmock: [Function: unmock],
         useFakeTimers: [Function: useFakeTimers],
         useRealTimers: [Function: useRealTimers]  

这告诉我什么: 1. 模拟工作(因为正确返回 true)或调用来自 React Native Keychain 的实际 setGenericPassword 函数。 2. 出于某种原因,keychainfunctions 被定义为jest.mock 对象,而不是三个模拟函数。

如果模拟有效,感觉 1 和 2 相互矛盾。 我究竟做错了什么?为什么这个模拟不起作用?如何解释这些奇怪的console.log()s 和失败的测试?

Bamse 在 cmets 中建议仅导出对象。此解决方法有效。我仍然会对如何正确地做到这一点/我做错了什么感兴趣。

const token = "abcdefghijklmnopqrstuvwxyz0123456789";
const credentials = 
  username: "session",
  password: token
;

export const setGenericPassword = jest.fn(
  (username, password) => new Promise((resolve, reject) => resolve(true)) // eslint-disable-line no-unused-vars
);

export const getGenericPassword = jest.fn(
  () => new Promise((resolve, reject) => resolve(credentials)) // eslint-disable-line no-unused-vars
);

export const resetGenericPassword = jest.fn(() => new Promise((resolve, reject) => resolve(true))); // eslint-disable-line no-unused-vars

【问题讨论】:

这个答案也可能有用:***.com/questions/45617362/… 如果你只想要一个返回值,你可以简单地使用jest.mock(module); import module from 'module'; module.mockReturnValue(Promise.resolve('response')); 的模块bypass 【参考方案1】:

您可以尝试在测试中使用jest.createMockFromModule 并仅模拟您需要的方法。 (以前在 26 之前的 Jest 版本中称为 genMockFromModule)

希望对你有帮助

【讨论】:

Bamse 不喜欢嘲弄,但无论如何都有帮助。 Lycklig den som har en sådan vän。 :-) 我认为如果在__mocks__/react-native-keychain.js 中您使用您想要的方法导出一个对象,您的模拟也会起作用。目前您正在导出 jest 模拟。 Visibleman 今天早上你让我笑了,让我想起了我的旧漫画书。 我试过这样使用它:const keyChainFunctions = jest.genMockFromModule("react-native-keychain").default; 但它也抛出了同样的错误。 你是对的。只需导出普通对象即可。我在问题中添加了它。

以上是关于笑话:如何正确模拟节点模块?的主要内容,如果未能解决你的问题,请参考以下文章

笑话:节点模块依赖使用导入语句并导致测试崩溃

笑话:当同一个模块也有命名导出时,如何模拟默认导出组件?

如何模拟尚不存在的节点模块?

如何处理模块和打字稿类型的笑话模拟功能

如何正确编码为啥使用节点请求模块的表情符号?

如何用笑话模拟 S3?