I have some code which uses the nodemailer module.
In the router (router.js), I have
const transporter = nodeMailer.createTransport(emailArgs);
Then inside the route (/login
) I have:
...
return transporter.sendMail(mailOptions);
I'm trying to test this route using the jest
testing framework. I'm having some trouble mocking out the call to sendMail
. I read this nice blogpost about how to use jest mocking, but I'm getting this error:
TypeError: Cannot read property 'sendMail' of undefined
And indeed when I check the value of transporter
it's undefined.
Here is my testing code (which doesn't work):
import request from "supertest";
import router from "./router";
jest.mock("nodemailer");
describe("", () => {
...
test("", async () => {
// 1 - 200 status code; 2 - check email was sent
expect.assertions(2);
const response = await request(router)
.post("/login")
// global variable
.send({ "email": email })
.set("Accept", "application/json")
.expect("Content-Type", /json/);
// should complete successfully
expect(response.status).toBe(200);
// TODO not sure how to express the expect statement here
});
});
So my question is how do I mock out a method of an instance of a class which is returned by a module?
I ran into the same problem and found a solution. Here is what I've discovered:
With jest.mock("nodemailer");
you tell jest to replace nodemailer
with an auto-mock. This means every property of nodemailer
is replaced with an empty mock function (similar to jest.fn()
).
That is the reason why you get the error TypeError: Cannot read property 'sendMail' of undefined
.
In order to have something useful, you have to define the mock function of nodemailer.createTransport
.
In our case we wan't to have an object with a property sendMail
. We could do this with nodemailer.createTransport.mockReturnValue({"sendMail": jest.fn()});
. Since you may want to test if sendMail
was called, it is a good idea to create that mock function before hand.
Here is a complete example of your testing code:
import request from "supertest";
import router from "./router";
const sendMailMock = jest.fn(); // this will return undefined if .sendMail() is called
// In order to return a specific value you can use this instead
// const sendMailMock = jest.fn().mockReturnValue(/* Whatever you would expect as return value */);
jest.mock("nodemailer");
const nodemailer = require("nodemailer"); //doesn't work with import. idk why
nodemailer.createTransport.mockReturnValue({"sendMail": sendMailMock});
beforeEach( () => {
sendMailMock.mockClear();
nodemailer.createTransport.mockClear();
});
describe("", () => {
...
test("", async () => {
// 1 - 200 status code; 2 - check email was sent
expect.assertions(2);
const response = await request(router)
.post("/login")
// global variable
.send({ "email": email })
.set("Accept", "application/json")
.expect("Content-Type", /json/);
// should complete successfully
expect(response.status).toBe(200);
// TODO not sure how to express the expect statement here
expect(sendMailMock).toHaveBeenCalled();
});
});
To mock nodemailer module I do
jest.mock('nodemailer', () => ({
createTransport: jest.fn().mockReturnValue({
sendMail: jest.fn().mockReturnValue((mailoptions, callback) => {})
})
}));
works like a charm
you can also define a mocked function if you need to evaluate .toBeCalledWith()
etc:
const sendMailMock = jest.fn()
jest.mock('nodemailer', () => ({
createTransport: jest.fn().mockImplementation(() => ({
sendMail: sendMailMock,
})),
}))
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