37

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
2
  • This answer also might be useful: stackoverflow.com/questions/45617362/… Commented Oct 29, 2018 at 10:44
  • If you just want a return value, you could simply bypass the module using jest.mock(module); import module from 'module'; module.mockReturnValue(Promise.resolve('response')); Commented May 28, 2019 at 16:15

1 Answer 1

15

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

Sign up to request clarification or add additional context in comments.

7 Comments

Bamse doesn't like mocking, but helps anyway. Lycklig den som har en sådan vän. :-)
I think your mocking would also work if in __mocks__/react-native-keychain.js you export an object with the methods you want. Currently you are exporting a jest mock. Visibleman you made me smile this morning and made me think about my old comic books.
I tried using it like this: const keyChainFunctions = jest.genMockFromModule("react-native-keychain").default; but it also threw the same error.
You are correct. Just exporting plain objects works. I added it in the question.
It says a function when called will return undefined. So my mocked function just keeps returning undefined no matter what. I've already pulled half my hair out trying to fix this. The only thing that really works for me is this: jest.mock('module', () => ({ myfunc: () => returnValue }));
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.