In jest, how do I mock an exported function to return a Promise rather than undefined?

Viewed 4937

I'm using jest and Typescript. I have this exported function in a service file ...

export async functionprocessData(
  data: MyDataI,
): Promise<
    ...

Then in a separate file (run-my-process.ts) that I call via npm cli, I have this

import {processData } from '../services/my.service';
...
         processData(data)
            .then((result) => {

Now I would like to mock the "processData" function from jest, so I tried this

jest.mock('../services/my.service', () => {
  // The mock returned only mocks the generateServerSeed method.
  const actual = jest.requireActual('../services/my.service');

  return {
      ...actual,
     processData: jest.fn().mockReturnValue(Promise.resolve({
        dataInserted: 1,
      }))
    }
});

...

describe('calls the job', function () {


  it('invokes the function', async () => {
    ...

    jest.spyOn(process, 'exit').mockImplementationOnce(() => {
      throw new Error('process.exit() was called.');
    });

    expect(() => {
      require('./run-my-process');
    }).toThrow('process.exit() was called.');

But the test dies with the error

ERROR [1624482717612] (71310 on localhost): Cannot read property 'then' of undefined

So it seems my function, processData, is somehow evaluating to "undefined" when called with an argument. What's the right way to mock my function to return a Promise and get my Jest test to pass?

5 Answers

Try this with whatever data you may need to adapt, a nice article here

processData: jest.fn(() => Promise.resolve({ data: {}}))

To export

export default jest.fn(() => Promise.resolve({ data: {} }));    

A much simpler approach could be:

import * as myService from '../services/my.service';

jest.spyOn(myService, 'processData').mockResolvedValue({ dataInserted: 1 });

Using this, you can bypass the complex mocking you are trying to do using jest.mock()

You can pass to jest.fn a function which returns a promise:

jest.mock('../services/my.service', () => ({
  ...jest.requireActual('../services/my.service'),
  processData: jest.fn(() => Promise.resolve({ dataInserted: 1 }))
}));

If you need to be able to change the resolved value per test, you can resolve a modifiable variable of the outer scope, or mock the resolved value with .mockResolvedValue or .mockResolvedValueOnce in the test.

Resolve a modifiable variable:

const data = {
  dataInserted: 1,
};

jest.mock('../services/my.service', () => ({
  ...jest.requireActual('../services/my.service'),
  processData: jest.fn(() => Promise.resolve({ data }))
}));

describe('...', () => {
  beforeEach(() => {
    data.dataInserted = 100;
  });

  it('...', () => {
  });
});

Mock the resolved value

import { processData } from '../services/my.service';

jest.mock('../services/my.service', () => ({
  ...jest.requireActual('../services/my.service'),
  processData: jest.fn()
}));

describe('...', () => {
  beforeEach(() => {
    (processData as jest.Mock).mockResolvedValueOnce({ dataInserted: 100 });
  });

  it('...', () => {
  });
});

I see some problems with some other answers, Typescript compiler may raise an error if the typing of the mocking function is not good.

Since you are using typescript and ts-jest, I'd like to suggest this solution using mocked https://kulshekhar.github.io/ts-jest/docs/guides/test-helpers

import { mocked } from "ts-jest/utils";
import { processData } from '../services/my.service';
jest.mock("../services/my.service");
const mockedProcessData = mocked(processData, true); // it has the correct type, I like to mock functions outside my tests but you can use mocked directly inside your tests if you prefer.

// inside your tests
mockedProcessData.mockResolvedValueOnce({
    dataInserted: 1,
})

I prefer to avoid jest.mock function because of problems with typings and implicity. Also I sometimes experienced unexpected behavior, jest.mock is magic thing and it can be used only in some rare cases where you don't have direct access to source code.

To make your code clean and explicit you can use dependency inversion pattern:

Service code:

export const runProcess = (mapper: (data: Data) => Promise<string>) => {
  // some code
}

Unit test code:

it('should work', () => {
  const mapper = jest.fn(() => Promise.resolve('testing'));


  expect(runProcess(mapper)).toEqual(...);

})

Client code:

import { runProcess } from './run-process';
import { dataMapper } from './data-mapper';

runProcess(dataMapper)

Also you can pass that data mapper as default value to your code:

import { dataMapper, DataMapper } from './data-mapper';
export const runProcess = (mapper: DataMapper = dataMapper) => {
  // some code
}

Then you don't need to pass mapper to your code everywhere, but in this case you are adding a dependency that ideally should be tested. At last try to answer yourself: Is it possible to write at least one integration test? Integration tests should be also presented because unit tests have low efficiency if you use only them, but they are great if you use them in combination with integration tests.

Related