Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock es6 class using Jest

I am attempting to mock a class Mailer using jest and I can't figure out how to do it. The docs don't give many examples of how this works. The process is the I will have a node event password-reset that is fired and when that event is fired, I want to send an email using Mailer.send(to, subject, body). Here is my directory structure:

project_root
-- __test__
---- server
------ services
-------- emails
---------- mailer.test.js
-- server
---- services
------ emails
-------- mailer.js
-------- __mocks__
---------- mailer.js

Here is my mock file __mocks__/mailer.js:

const Mailer = jest.genMockFromModule('Mailer');

function send(to, subject, body) {
  return { to, subject, body };
}

module.exports = Mailer;

and my mailer.test.js

const EventEmitter = require('events');
const Mailer = jest.mock('../../../../server/services/emails/mailer');

test('sends an email when the password-reset event is fired', () => {
  const send = Mailer.send();
  const event = new EventEmitter();
  event.emit('password-reset');
  expect(send).toHaveBeenCalled();
});

and finally my mailer.js class:

class Mailer {

  constructor() {
    this.mailgun = require('mailgun-js')({
      apiKey: process.env.MAILGUN_API_KEY,
      domain: process.env.MAILGUN_DOMAIN,
    });
  }

  send(to, subject, body) {
    return new Promise((reject, resolve) => {
      this.mailgun.messages().send({
        from: 'Securely App <[email protected]>',
        to,
        subject: subject,
        html: body,
      }, (error, body) => {
        if (error) {
          return reject(error);
        }

        return resolve('The email was sent successfully!');
      });
    });
  }

}

module.exports = new Mailer();

So, how do I successfully mock and test this class, using Jest? Many thanks for helping!

like image 348
dericcain Avatar asked May 03 '17 01:05

dericcain


People also ask

How do you mock an ES6 class Jest?

Jest can be used to mock ES6 classes that are imported into files you want to test. ES6 classes are constructor functions with some syntactic sugar. Therefore, any mock for an ES6 class must be a function or an actual ES6 class (which is, again, another function). So you can mock them using mock functions.

How do you mock a method in Jest?

The simplest and most common way of creating a mock is jest. fn() method. If no implementation is provided, it will return the undefined value. There is plenty of helpful methods on returned Jest mock to control its input, output and implementation.

What is __ mocks __ in Jest?

Mocking Node modules​ If the module you are mocking is a Node module (e.g.: lodash ), the mock should be placed in the __mocks__ directory adjacent to node_modules (unless you configured roots to point to a folder other than the project root) and will be automatically mocked. There's no need to explicitly call jest.


2 Answers

You don't have to mock your mailer class but the mailgun-js module. So mailgun is a function that returns the function messages that return the function send. So the mock will look like this.

for the happy path

const happyPath = () => ({   messages: () => ({     send: (args, callback) => callback()   }) }) 

for the error case

const errorCase = () => ({   messages: () => ({     send: (args, callback) => callback('someError')   }) }) 

as you have this 2 cases it make sense to mock the module inside your test. First you have to mock it with a simple spy where we later can set the implementation for our cases and then we have to import the module.

jest.mock('mailgun-js', jest.fn()) import mailgun from 'mailgun-js' import Mailer from '../../../../server/services/emails/mailer' 

As your module uses promises we have 2 options either return the promise from the test or use async/await. I use the later one for more info have a look here.

test('test the happy path', async() => {  //mock the mailgun so it returns our happy path mock   mailgun.mockImplementation(() => happyPath)   //we need to use async/awit here to let jest recognize the promise   const send = await Mailer.send();   expect(send).toBe('The email was sent successfully!') }); 

If you would like to test that the mailgun send method was called with the correct parameter you need to adapt the mock like this:

const send = jest.fn((args, callback) => callback()) const happyPath = () => ({   messages: () => ({     send: send   }) }) 

Now you could check that the first parameter for send was correct:

expect(send.mock.calls[0][0]).toMatchSnapshot() 
like image 102
Andreas Köberle Avatar answered Sep 22 '22 16:09

Andreas Köberle


Just for Googlers and future visitors, here's how I've setup jest mocking for ES6 classes. I also have a working example at github, with babel-jest for transpiling the ES module syntax so that jest can mock them properly.

__mocks__/MockedClass.js

const stub = {
  someMethod: jest.fn(),
  someAttribute: true
}

module.exports = () => stub;

Your code can call this with new, and in your tests you can call the function and overwrite any default implementation.

example.spec.js

const mockedClass = require("path/to/MockedClass")(); 
const AnotherClass = require("path/to/AnotherClass");
let anotherClass;

jest.mock("path/to/MockedClass");

describe("AnotherClass", () => {
  beforeEach(() => {
    mockedClass.someMethod.mockImplementation(() => {
      return { "foo": "bar" };
    });

    anotherClass = new AnotherClass();
  });

  describe("on init", () => {
    beforeEach(() => { 
      anotherClass.init(); 
    });

    it("uses a mock", () => {
      expect(mockedClass.someMethod.toHaveBeenCalled();
      expect(anotherClass.settings)
        .toEqual(expect.objectContaining({ "foo": "bar" }));
    });
  });

});
like image 22
Justus Romijn Avatar answered Sep 25 '22 16:09

Justus Romijn