I setup an endpoint which allows the user to reset their password. Everything works and the tests were passing UNTIL I added nodemailer and included a line which sends the user an email.
I am using Jest for my tests.
If I comment out the line which sends the emails the tests pass, mailer.sendPasswordResetEmail(body.email, token);
if I leave the line in - my tests fail. I have confirmed with REST client that everything is working properly which leads me to believe the test is the issue.
ReferenceError: You are trying to import a file after the Jest environment has been torn down.
test("if a valid user requests a password change, they should be assigned a hash", async () => {
const userBefore = await helper.getUser();
expect(userBefore.passwordResetHash).toBe("");
await api
.post("/api/reset-password")
.send({ email: userBefore.email })
.expect(200);
const userAfter = await helper.getUser();
expect(userAfter.passwordResetHash).not.toBeNull();
});
I think that I am not mocking nodemailer properly - does anyone have any experience using nodemailer and jest together? or is there a better way to do it
The files in question are controllers/resetPassword.js
, utils/mailer.js
and tests/resetPassword.test.js
.
controllers/resetPassword.js
resetPasswordRouter.post("/", async (request, response) => {
// get email from request
const { body } = request;
// get the user with matching email
const user = await User.findOne({ email: body.email });
// if not found return error
if (!user) {
return response.status(400).json({ error: "User not found" });
}
// if user generate a token
const token = helper.generateToken();
// create a new user object with the resetPasswordHash defined
const update = {
passwordResetHash: await bcrypt.hash(token, 10),
};
// update user model with the password hash
const updatedUser = await User.findByIdAndUpdate(user.id, update, {
new: true,
});
mailer.sendPasswordResetEmail(body.email, token);
// setup timer to reset password hash in 30 minutes
setTimeout(async () => {
await User.findByIdAndUpdate(
user.id,
{ passwordResetHash: "" },
{ new: true }
);
}, 30000); // half hour
// return the updated user with the hash set
response.status(200).json(updatedUser);
});
utils/mailer.js
const nodemailer = require("nodemailer");
const config = require("../utils/config");
const mailer = nodemailer.createTransport({
host: "smtp.mailtrap.io",
port: 1111,
auth: {
user: "8b4f30425e75ea",
pass: "8b4f30425e75ea",
},
});
const sendPasswordResetEmail = (email, token) => {
const sitename = config.SITENAME;
const resetPasswordLink = `${sitename}/api/reset-password/verify?email=${email}&token=${token}`;
mailer.sendMail({
to: email,
from: config.FROM_EMAIL,
subject: `Password Reset | ${sitename}`,
html: `<h1>Password Reset</h1>
<p>Hello, you\'ve requested a password reset.</p>
<p><a href="${resetPasswordLink}">Click here to reset your password</a>, if you did not make this request please disregard the email.</p>`,
});
};
module.exports = {
sendPasswordResetEmail,
};
You can find the repository here: https://github.com/gerrgg/gregpress
You need to mock the function mailer.sendPasswordResetEmail
so when the controller calls it, it'll actually call your mock implementation.
test("if a valid user requests a password change, they should be assigned a hash", async () => {
const userBefore = await helper.getUser();
expect(userBefore.passwordResetHash).toBe("");
// Jest spy to intercept mailer.sendPasswordResetEmail call
let spy = jest.spyOn(mailer, 'sendPasswordResetEmail').mockImplementation(() => return true);
await api
.post("/api/reset-password")
.send({ email: userBefore.email })
.expect(200);
const userAfter = await helper.getUser();
expect(userAfter.passwordResetHash).not.toBeNull();
});