danwillm
danwillm

Reputation: 489

Mocking function object with jest

I currently have an object that is used to interact with my API api.js:

export var Auth = (function () {
    var Login = async function(username, password) {
        //Async login code for interacting with the API
    };
    return {
        Login: Login
    }
});

And this object is imported inside another file, login.js:

import * as API from './api';

export var LoginRequestHandler = function() {
   //processess user input, then calls:
   try {
       await API.Auth().Login(username, password);
   } catch(e) {
       throw new Error(e);
}

This is my jest test:

import * as API from '../api';
import * as User from '../user';

jest.mock('../api');

const spy = jest.spyOn(API.Auth(), 'Login');
    User.LoginRequestHandler().then(() => {
        expect(spy).toHaveBeenLastCalledWith('theUsername', 'thePassword');
    }).catch(error => console.log(error));

This is my mock file, __mock__/api.js:

export var Auth = (function () {
    var Login = async function(username, password) {
        return Promise.resolve(true);
    };
    return {
        Login: Login
    }
});

I retrieve theUsername and thePassword through document.getElementId() in the LoginRequestHandler and create my own DOM for the test above.

Adding console.log(username) in the LoginRequestHandler reveals that it is being called and is able to get the right values. Furthermore, adding a console.log(username) in API.Auth().Login also reveals that it is getting the right values as well. However, when I look at my test logs, I see: Number of calls: 0 for the mock function and the test results in errors.

I assume that I am trying to spy on the wrong function, and is there anyway that I can fix this?

Upvotes: 1

Views: 2879

Answers (1)

Lin Du
Lin Du

Reputation: 102597

Every time you call API.Auth(), it will return a new object which has a Login method. So, the object created in LoginRequestHandler function and created by jest.spyOn(API.Auth(), 'Login') statement in your test case are different. The spy is only added to the later one. The Login method in LoginRequestHandler function is not spied.

So, here I am going to use jest.mock() to mock the api.js module without putting mocked object to __mocks__ directory. E.g.

api.js:

export var Auth = function () {
  var Login = async function (username, password) {};

  return {
    Login: Login,
  };
};

user.js:

import * as API from './api';

export var LoginRequestHandler = async function () {
  const username = 'theUsername';
  const password = 'thePassword';
  try {
    await API.Auth().Login(username, password);
  } catch (e) {
    throw new Error(e);
  }
};

user.test.js:

import * as API from './api';
import * as User from './user';

jest.mock('./api', () => {
  const auth = { Login: jest.fn() };
  return {
    Auth: jest.fn(() => auth),
  };
});

describe('61643983', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });
  it('should login', () => {
    expect.assertions(2);

    return User.LoginRequestHandler().then(() => {
      expect(API.Auth).toBeCalledTimes(1);
      expect(API.Auth().Login).toHaveBeenLastCalledWith('theUsername', 'thePassword');
    });
  });

  it('should throw error', () => {
    expect.assertions(4);
    const mError = new Error('user not found');
    API.Auth().Login.mockRejectedValueOnce(mError);

    return User.LoginRequestHandler().catch((e) => {
      expect(API.Auth).toBeCalled();
      expect(API.Auth().Login).toHaveBeenLastCalledWith('theUsername', 'thePassword');
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/user not found/);
    });
  });
});

unit test results with 100% coverage:

 PASS  stackoverflow/61643983/user.test.js (11.507s)
  61643983
    ✓ should login (5ms)
    ✓ should throw error (3ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 user.js  |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        13.023s

source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61643983

Upvotes: 1

Related Questions