Jest unit test to spy on lower-level method (NodeJS)

醉酒当歌 提交于 2019-12-02 14:13:34

问题


Trying to spy and override a function two levels down using Jest.

The test results say, "Expected mock function to have been called, but it was not called."

// mail/index.unit.test.js
import mail from './index';
import * as sib from '../sendinblue';

describe('EMAIL Util', () =>
  test('should call sibSubmit in server/utils/sendinblue/index.js', async() => {
    const sibMock = jest.spyOn(sib, 'sibSubmit');
    sibMock.mockImplementation(() => 'Calling sibSubmit()');
    const testMessage = {
      sender: [{ email: 'foo@example.com', name: 'Something' }],
      to: [{ email: 'foo@example.com', name: 'Something' }],
      subject: 'My Subject',
      htmlContent: 'This is test content'
    };
    await mail.send(testMessage);
    expect(sibMock).toHaveBeenCalled();
  })
);

mail.send() comes from here...

// mail/index.js
import { sibSendTransactionalEmail } from '../sendinblue';

export default {
  send: async message => {
    try {
      return await sibSendTransactionalEmail(message);
    } catch(err) {
      console.error(err);
    }
  }
};

Which uses SendInBlue's API via axios (why I need to mock)...

// sendinblue/index.js
import axios from 'axios';
import config from '../../config/environment';

export async function sibSubmit(method, url, data) {
  let instance = axios.create({
    baseURL: 'https://api.sendinblue.com',
    headers: { 'api-key': config.mail.apiKey }
  });
  try {
    const response = await instance({
      method,
      url,
      data
    });
    return response;
  } catch(err) {
    console.error('Error communicating with SendInBlue', instance, err);
  }
}

export const sibSendTransactionalEmail = message => sibSubmit('POST', '/v3/smtp/email', message);

I assumed mail.send() would call sibSendTransactionalEmail() in the other module and it would call sibSubmit(), the focus of jest.spyOn(). Wondering where I went wrong.


回答1:


jest.spyOn replaces the method on the object it is passed with a spy.

In this case you are passing sib which represents the ES6 module exports from sendinblue.js, so Jest will replace the module export for sibSubmit with the spy and give the spy the mock implementation you provided.

mail.send then calls sibSendTransactionalEmail which then calls sibSubmit directly.

In other words, your spy is not called because sibSendTransactionalEmail does not call the module export for sibSubmit, it is just calling sibSubmit directly.

An easy way to resolve this is to note that "ES6 modules support cyclic dependencies automatically" so you can simply import the module into itself and call sibSubmit from within sibSendTransactionalEmail using the module export:

import axios from 'axios';
import config from '../../config/environment';
import * as sib from './';  // import module into itself

export async function sibSubmit(method, url, data) {
  let instance = axios.create({
    baseURL: 'https://api.sendinblue.com',
    headers: { 'api-key': config.mail.apiKey }
  });
  try {
    const response = await instance({
      method,
      url,
      data
    });
    return response;
  } catch(err) {
    console.error('Error communicating with SendInBlue', instance, err);
  }
}

export const sibSendTransactionalEmail = message => sib.sibSubmit('POST', '/v3/smtp/email', message);  // call sibSubmit using the module export

Note that replacing ES6 module exports with jest.spyOn like this works because Jest transpiles the ES6 modules to Node modules in a way that allows them to be mutated




回答2:


Another way to work around this problem is to rewire the function you're spying on within the module, which is nicer since you don't have to modify the original code for the purposes of testing. You can use the rewire module if before ES6, or babel-rewire for ES6:

// mail/index.unit.test.js
import mail from './index';
import * as sib from '../sendinblue';

describe('EMAIL Util', () =>
  test('should call sibSubmit in server/utils/sendinblue/index.js', async() => {
    const sibMock = jest.spyOn(sib, 'sibSubmit');
    sibMock.mockImplementation(() => 'Calling sibSubmit()');
    //============ force the internal calls to use the mock also
    sib.__set__("sibSubmit", sibMock);  
    //============
    const testMessage = {
      sender: [{ email: 'foo@example.com', name: 'Something' }],
      to: [{ email: 'foo@example.com', name: 'Something' }],
      subject: 'My Subject',
      htmlContent: 'This is test content'
    };
    await mail.send(testMessage);
    expect(sibMock).toHaveBeenCalled();
  })
);


来源:https://stackoverflow.com/questions/54414088/jest-unit-test-to-spy-on-lower-level-method-nodejs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!