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!
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.
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.
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.
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()
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" }));
});
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With