Express Jest and Supertest how to mock middleware with argument - node.js

I have been trying for several hours to test an endpoint of my rest api with Jest, Supertest and Express.
This endpoint is protected by an authentication middleware named "advancedAuthGuard".
So I'm trying to mock this middleware in order to skip authentication check for endpoints testing
//./router.js
router.get('/route1', advancedAuthGuard(false), controller);
Important: advancedAuthGuard is a middleware that accepts configuration argument ( curried middleware )
//./middleware/advancedAuthGuard.js
const advancedAuthGuard = (additionalCondition) => (req,res,next) => {
//Check authentication logic ...
const isAuth = true
if (isAuth && !additionalCondition)
next()
else
next(new Error('Please auth'))
}
When I run the following test to check if I get status code '200' . The test fail before run.
//./test.js
import supertest from "supertest"
import app from './app.js'
import { advancedAuthGuard } from "./middlewares/advancedAuthGuard";
jest.mock("./middlewares/advancedAuthGuard")
const request = supertest(app)
beforeEach(()=>{
jest.clearAllMocks()
})
it("should '/route1' respond with 200 status", async ()=>{
const mockedMiddleware = jest.fn().mockImplementation((res, req, next)=> next() )
advancedAuthGuard.mockReturnValue(mockedMiddleware)
const res = await request.get("/route1")
expect(res.status).toEqual(200)
})
I get the following error:
Test suite failed to run
Route.get() requires a callback function but got a [object Undefined]
> 10 | router.get('/route1', advancedAuthGuard(false), controller);
| ^
I therefore deduce that the problem comes from the mock...
And when I debug it via console.log, I realize that I get an incomplete mock :
I was able to verify that the mock of function (advancedAuthGuard(false)) was present
But this mock should return a second mock of type function (req,res,next){}, however it only returns undefined
I also noticed that:
This mock issue only occurs with curried middlewares (middleware with input parameters)
This mock issue only occurs when the curried middleware is executed in express router. (When I tried the mock outside express router, the mock appears correctly)
So I would like to know why it is impossible for me to mock a curried middleware ( middleware with argument ) used in an Expressjs endpoint with Jest and Supertest.
Here is a github link with minimal express, jest, supertest config, where you can reproduce this problem, by running the test.js file. https://github.com/enzo-cora/jest-mock-middleware

Not sure why it is not working the way you tried but if you pass the mock implementation to the autoMock function it will do the trick.
import supertest from "supertest";
import app from "./app";
jest.mock("./middlewares/simpleAuthGuard", () => ({
simpleAuthGuard: (_, __, next) => next()
}));
jest.mock("./middlewares/advancedAuthGuard", () => ({
advancedAuthGuard: () => (_, __, next) => next()
}));
const request = supertest(app);
describe('My tests', () => {
beforeEach(() => jest.clearAllMocks());
it("should '/route1' with advanced middleware work", async () => {
const res = await request.get("/route1");
expect(res.status).toEqual(200);
});
it("should '/route2' with simple middleware work", async () => {
const res = await request.get("/route2")
expect(res.status).toEqual(200)
});
});

Related

mocking a api call not returning data

Trying to mock the following api to return data.
I keep getting the error:
ReferenceError: Cannot access 'mockedApi' before initialization
Mock Code
const mockedApi = jest.fn();
jest.mock("../../../utils/api", () => ({
...jest.requireActual("../../../utils/api"),
get: jest.fn(),
}));
When I wrap it in a function then the response doesn't work.
const mockedApi = jest.fn();
jest.mock("../../../utils/api", () => ({
...jest.requireActual("../../../utils/api"),
get: () => mockedApi,
}));
when I do a log on the api its showing get as a function now that doesn't return anything. when is should be returning data if I was to use. ?
mockedApi.mockResolvedValueOnce({ data: 'hello });
Do I even need to use ...jest.requireActual("../../../utils/api")
I thought this would insure the original methods would not get mocked and only the once I add would be mocked like get. But this doesn't seem to be the case the entire file and all its methods get mocked ?
File been mocked
import axios from "axios";
const api = axios.create({
baseURL: process.env.REACT_APP_SPARQL_URL,
responseType: "json",
});
export const encode = (arr) => {
const urlencoded = new URLSearchParams();
arr.forEach((item) => {
urlencoded.append(...Object.keys(item), ...Object.values(item));
});
return urlencoded;
};
export default api;

Unable to run mockttp with Cypress

I'm trying to run the mockttp with cypress. I used the example that's listed in project github. I changed the port to run on 3000 but I am getting an error saying Cannot add rules before the server is started.
/*global cy:true,before:true*/
/// <reference path="../../node_modules/cypress/types/index.d.ts" />
const superagent = require("superagent");
const mockServer = require("mockttp").getLocal();
describe('mockttp test' , () => {
beforeEach(() => {
mockServer.start(3000);
});
afterEach(() => {
mockServer.stop();
});
it("lets you mock requests, and assert on the results", async () => {
// Mock your endpoints
const mockedPath = mockServer.forGet("/mocked-path");
// ERROR OCCURS HERE
await mockedPath.thenReply(200, "A mocked response");
// Make a request
const response = await superagent.get("http://localhost:3000/mocked-path");
// Assert on the results
expect(response.text).to.equal("A mocked response");
});
});
You need to wait until the server is actually started before running your test, by waiting for the promises returned by mockServer.start() (and by .stop()).
You can either make your beforeEach & afterEach functions async and then await those lines, or you can just add return to return the promise so that Mocha waits for them automatically.

How to mock a function only once in a test suite?

I'm writing integration tests for a project. Within one test suite, I'm invoking a register endpoint in multiple tests. Most of the time I want to test what the actual response of the registerUser function is given certain req parameters.
This all works fine except I also want to test what happens if the registerUser function throws an error. I know I can mock the registerUser function on top of the test suite but this will affect all tests. I've tried to play around with jest.mock and jest.spyOn but I could not get it to work yet.
How can I mock the response of the registerUser function once and restore it afterwards so it doesn't affect the other tests in the suite?
authController.js
router.post('/register', async (req, res) => {
try {
const response = await registerUser(req);
res.status(HttpStatus.OK).json({ response });
} catch (err) {
res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ err });
}
});
authController.test.js
const faker = require('faker');
const HttpStatus = require('http-status-codes');
const authService = require('../services/authService');
// -- Tests where the response of the registerUser function are not mocked are here -- //
it('Gives a status code 500 when an unexpected error is thrown', async () => {
const registerUserMock = jest.spyOn(authService, "registerUser");
registerUserMock.mockReturnValue(() => new Error('Oh snap! Something went wrong.'));
const res = await agent.post('/register')
.send({
email: faker.internet.email(),
firstname: faker.name.firstName(),
lastname: faker.name.lastName(),
password: '123',
reTypedPassword: '123',
});
expect(res.statusCode).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
registerUserMock.mockRestore();
});
// -- more tests -- //
Easiest way would be to
group the tests which should use the same mocked response in a suite (describe)
mock the response in that suite's beforeAll hook and save the mock instance
restore the original implementation in that suite's afterAll hook.
describe('tests with successful auth result', () => {
let authSpy;
beforeAll(() => {
authSpy = jest.spyOn(authService, "registerUser").mockReturnValue(...);
});
afterAll(() => {
authSpy.mockRestore();
});
// tests using successful result
});
describe('tests with failing auth result', () => {
// same but with different spy result
});
note two important things:
you need to call mockRestore on the mock instance returned from mockReturnValue, not on the initial spy value
it's best to setup the mock in beforeEach / beforeAll and restore it in afterEach /afterAll, because if you set and restore it directly in the test (it), then if the test fails the spy remains unrestored, and may affect the following tests!

jest.setTimeout.Error: Mocking Express middleware with Jest and Supertest

I would like to mock the auth middleware function to always just call next(). To try and acheive this, I added the following to the beginning of my test file before the auth middleware function is added to the app in app.js.
jest.mock('../../middleware/auth.js', () =>
// ensure func is mocked before being attached to the app instance
jest.fn((req, res, next) => next()) // seems to only work for first request that hits this middleware
); // Mock authentication
I then added some debugs in the auth middleware but did not hit them for any of the tests.
Currently I am using the following, when the beforeEach() function is not commented out the tests all pass:
role.test.js
jest.mock('../../middleware/auth.js', () =>
// ensure func is mocked before being attached to the app instance
jest.fn((req, res, next) => next()) // seems to only work for first request that hits this middleware
); // Mock authentication
const request = require('supertest');
const app = require('../../app.js');
const Role = require('../../../db/models/index.js').Role;
// const auth = require('../../middleware/auth.js');
/**
* Test role API routes
*/
describe('role-router', () => {
let res;
const token = 'valid-token';
const baseUrl = '/private/api/roles';
// Without the `beforeEach()` only the first request sent using supertest will use the mocked functionality
// With the `beforeEach()` everything functions as expected, but why?
// beforeEach(() => {
// auth.mockImplementation((req, res, next) => next());
// });
describe(`Create new role | post ${baseUrl}`, () => {
describe('When successful', () => {
beforeAll(async () => {
// this will use the proper mocked functionality
res = await request(app)
.post(baseUrl)
.set('Authorization', token)
.send({
roleName: 'Secret Agent',
roleDesc: "World's best secret agent",
...
});
});
// passes
it('Should return 201', () => {
expect(res.status).toBe(201);
});
}); // When successful
}); // Create new role
describe(`Delete role by id | delete ${baseUrl}/:id`, () => {
describe('When successful', () => {
beforeAll(async () => {
const role = await Role.create({
roleName: 'Secret Agent',
roleDesc: "World's best secret agent",
...
});
// fails does not get response, res remains the same as res from previous test
res = await request(app)
.delete(`${baseUrl}/${role.id}`)
.set('Authorization', token)
.send();
});
// fails with 201
it('Should return 204', () => {
expect(res.status).toBe(204);
});
}); // When successful
}); // Delete role
});
Error received:
● role-router › Delete role by id | delete /private/api/roles/:id › When successful › Should return 204
Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.
at mapper (node_modules/jest-jasmine2/build/queueRunner.js:29:45)
app.js
// Dependencies
const express = require('express');
const auth = require('./middleware/auth.js');
...
/**
* Express application
*/
const app = express();
// Middleware
app.use(express.json());
...
// Private routes are secured with jwt authentication middleware
app.all('/private/*', (req, res, next) => auth(req, res, next));
// Public routes
...
// Private routes
app.use('/private/api/roles/', require('./components/role/role-router.js'));
...
module.exports = app;
Any ideas why this is not working without using the beforeEach() function to mock the middleware functionality before every test? Perhaps I am missing something more serious?
The reason for such behaviour is that there is spy.mockReset(), jest.resetAllMocks() or enabled resetMocks configuration option. Use restoreMocks option instead for a reasonable default configuration.
The difference between jest.fn(() => ...) and jest.fn().mockImplementation(() => ...) is how they respond to jest.restoreAllMocks(). restoreAllMocks restores original implementation which is a no-op in case of jest.fn(), while () => ... is considered original implementation in case of jest.fn(...). So normally jest.fn(() => ...) implementation isn't expected to be reset.
The difference between jest.resetAllMocks and jest.restoreAllMocks is that the former can make jest.fn(...) a noop but doesn't restore spies to original implementations. This specifically affects spies that are provided in mocked modules once per test suite with jest.mock, yet still doesn't restore global spies. Since this behaviour is rarely desirable, jest.restoreAllMocks() or restoreMocks configuration option are generally preferable. In case there's ever a need for jest.fn(...) implementation to be reset, mockReset or mockImplementation can be done specifically for this spy.

mock nodemailer.createTransport.sendMail with jest

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,
})),
}))
well I still wanted my mailer to work and returning undefined was not working, so I had to change sendMailMock to this:
const sendMailMock = jest.fn((mailOptions, callback) => callback());
This worked for me
Create a mock file at the directory mocks/nodemailer.js (See Jest Manual Mock for reference)
Add the following code to the file. The createTransport method needs to return a response that has a method sendMail for it to work. So see the code used below
class CreateTransportClass {
sendMail(){
//console.log("mocked mailer");
}
}
const createTransport = ()=>{
return new CreateTransportClass()
}
module.exports = {
createTransport
}
In the jest config file (jest.config.js) add the file path to the testPathIgnorePatterns like this:
{
testPathIgnorePatterns: ["/__mocks__/nodemailer.js"],
}
This should work perfectly.

Resources