Rahul
Rahul

Reputation: 5854

Mock a function from another file - Jest

I am writing Unit test cases for my application. There is one function which is written in Utils section and Used in all files. I wanted to mock this Utils function whenever I need but I am unable to do so.

Here is my code setup:

Utils.js

> const getData = (name) => "Hello !!! " + name;
> 
> const getContact = ()=> return Contacts.mobile;
> 
> export {
>     getData,
>     getContact }

Login.js (Which uses Utils.js)

    const welcomeMessage = (name) => {

    return getData(name);
    }

My Test file (Login.spec.js)

import { getData } from '../../src/utils';


jest.mock('getData', () => jest.fn())


describe('User actions', () => {

    it('should get username', () => {
        const value = 'Hello !!! Jest';
        expect(welcomeMessage('Jest')).toEqual(value);
    });

});

When I run my test case then I am getting this error:

 Cannot find module 'getData' from 'Login.spec.js'

I tried to find the solution on official Jest Documentation and on SO as well but couldn't found anything. I am unable to fix this error and mock this function.

Upvotes: 45

Views: 116040

Answers (4)

karlosos
karlosos

Reputation: 1194

Using jest.spyOn fails in 2022

I was trying to recreate sherwin waters solution using jest.spyOn but it was not working. Don't know why.

UPDATE: Now I know why. I was using Create-React-App and with new version they changed default flag resetMocks is now set to true by default source. That's why we need to declare them in beforeEach as they are cleaned after each test.

// ./demo/welcomeMessage.js
import { getData } from "./utils"

export const WelcomeMessage = ({name}) => {
    return getData(name)
}

// ./demo/utils.js
const getData = (name) => `Hello ${name}!`;

export { getData }

// ./App.js
import { WelcomeMessage } from "./demo/welcomeMessage";

function App() {
  return (
    <div className="App">
      <h1>My app</h1>
      <p>
        <WelcomeMessage name={'John'} />
      </p>
    </div>
  );
}

export default App;

// ./App.test.js
import { render, screen } from '@testing-library/react';
import App from './App';
import * as utils from "./demo/utils";

jest.spyOn(utils, "getData").mockReturnValue("mocked message");  // this doesn't work as intended

describe('App', () => {
  test('renders header', () => {
    render(<App />);
    expect(screen.getByText(/My app/i)).toBeInTheDocument()
  });

  test('renders correct welcome message', () => {
    render(<App />)
    expect(screen.getByText(/mocked message/i)).toBeInTheDocument()
  });
})

Solution #1 using jest.spyOn in beforeEach

Wrap jest.spyOn in beforeEach block

beforeEach(() => {
  jest.spyOn(utils, "getData").mockReturnValue("mocked message");
});

Now tests should work correctly. This is similar to this Stack Overflow post.

Solution #2 using jest.mock

Instead of using import * as ... we can mock our module with jest.mock. Following test works fine:

import { render, screen } from '@testing-library/react';
import App from './App';

jest.mock('./demo/utils', () => ({
    getData: () => 'mocked message'
}));

describe('App', () => {
  test('renders header', () => {
    render(<App />);
    expect(screen.getByText(/My app/i)).toBeInTheDocument()
  });

  test('renders correct welcome message', () => {
    render(<App />)
    expect(screen.getByText(/mocked message/i)).toBeInTheDocument()
  });
})

This approach is harder to use if we want to have multiple mocked implementations, e.g. one for testing failing cases and one for normal cases. We would need to use doMock and async imports.

Upvotes: 23

s00103898-276165-15433
s00103898-276165-15433

Reputation: 998

I got the same question and finally I find the solution. I post it here for the one who got the same issue.

Jest test file:

import * as utils from "./demo/utils";
import { welcomeMessage } from "./demo/login";

// option 1: mocked value
const mockGetData = jest.spyOn(utils, "getData").mockReturnValue("hello");

// option 2: mocked function
const mockGetData = jest.spyOn(utils, "getData").mockImplementation((name) => "Hello !!! " + name);

describe("User actions", () => {
    it("should get username", () => {
        const value = "Hello !!! Jest";
        expect(welcomeMessage("Jest")).toEqual(value);
    });
});

references: https://jestjs.io/docs/jest-object#jestfnimplementation

jest.spyOn(object, methodName)

Creates a mock function similar to jest.fn but also tracks calls to object[methodName]. Returns a Jest mock function.

Note: By default, jest.spyOn also calls the spied method. This is different behavior from most other test libraries. If you want to override the original function, you can use:

jest.spyOn(object, methodName).mockImplementation(() => customImplementation)

or

object[methodName] = jest.fn(() => customImplementation);

Upvotes: 36

Khoa
Khoa

Reputation: 2932

The first argument of jest.mock(...) must be a module path:

jest.mock('../../src/utils');

because the utils module is your code, not a 3rd lib, so you must learn manual mock of jest: https://facebook.github.io/jest/docs/en/manual-mocks.html

if you had this file: src/utils.js

you can mock it by creating a file: src/__mocks__/utils.js

content of this file is the replication of the original but replace the implementation by getData = jest.fn()

on you test file, just call: jest.mock('../../src/utils'); at begin of file.

then when you're familiar with, you can call that function inside beforeEach() and call its counter jest.unmock('../../src/utils'); insider afterEach()

An easy way to think about it is that:

when you call jest.mock('../../src/utils');, it means you tell jest that:

hey if the running test meets the line require('../../src/utils'), don't load it, let load ../../src/__mocks__/utils.

Upvotes: 43

Dan
Dan

Reputation: 1575

another solution would fake this by doing something like:

window['getData'] = jest.fn();

Upvotes: 1

Related Questions