Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest: How to correctly mock a node module?

I want to mock the node_module 'React Native Keychain' in React Native with Jest.

Following the docs I created a folder called __mocks__ and created a file called react-native-keychain.js in it.

Here is the code within the file:

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   }; }); 

I then wrote my tests for the functions that use this library:

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);         });       });     }); 

The problem is, that the test fails. I get the error message:

 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) 

And the console.log() yield:

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] } } 

What this tells me: 1. Either the mock works (because true is returned correctly) or the actual setGenericPassword function from React Native Keychain is being called. 2. For some reason, keychainfunctions is defined, but as the jest.mock object instead of the three mocked functions.

It feels like 1 and 2 contradict each other, if the mock works. What am I doing wrong? Why does this mock not work? How can these weird console.log()s and failed tests be explained?

Bamse suggested in the comments to just export objects. This workaround works. I would still be interested how to correctly do it / what I did wrong.

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 
like image 757
J. Hesters Avatar asked Jul 24 '18 07:07

J. Hesters


People also ask

How do you mock a Node module?

Mocking Node modules​ Scoped modules (also known as scoped packages) can be mocked by creating a file in a directory structure that matches the name of the scoped module. For example, to mock a scoped module called @scope/project-name , create a file at __mocks__/@scope/project-name.

How do you do mocking in Jest?

Mocking Modules​ export default Users; Now, in order to test this method without actually hitting the API (and thus creating slow and fragile tests), we can use the jest. mock(...) function to automatically mock the axios module.

How do you mock react a component in Jest?

To mock a React component, the most straightforward approach is to use the jest. mock function. You mock the file that exports the component and replace it with a custom implementation. Since a component is basically a function, the mock should also return a function.


1 Answers

You could try to use jest.createMockFromModule in your test and mock only the method you need. (previously in Jest versions prior to 26 called genMockFromModule)

Hope it helps

like image 137
bamse Avatar answered Sep 25 '22 14:09

bamse