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.
Related
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)
});
});
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.
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!
I'm trying to follow this example: https://www.alexjamesbrown.com/blog/development/stubbing-middleware-testing-express-supertest/ but the sinon stub doesn't seem to be executing the wrapped code. I've seen lots of stackoverflow posts regarding this issue but none of the answers have helped me figure out what I'm doing wrong. Whenever I run my test I get the following error:
1) should return a list of sites
0 passing (42ms) 1 failing
GET /api/config/buildPro/sites
should return a list of sites:
Error: expected 200 "OK", got 403 "Forbidden"
at Test._assertStatus (node_modules\supertest\lib\test.js:268:12)
at Test._assertFunction (node_modules\supertest\lib\test.js:283:11)
at Test.assert (node_modules\supertest\lib\test.js:173:18)
at Server.localAssert (node_modules\supertest\lib\test.js:131:12)
at emitCloseNT (net.js:1655:8)
at processTicksAndRejections (internal/process/task_queues.js:83:21)
This leads me to believe it's not calling the stub code but instead executes the actual authorization function. Here's my code:
app.js
const express = require('express');
const app = express();
const authorization = require('./security/authorization');
const configRoutes = require('./api/routes/config');
app.all('/api/*', authorization.authorize);
app.use('/api/config', configRoutes);
module.exports = app;
authorization.js
const aad = require('azure-ad-jwt');
module.exports.authorize = (req, res, next) => {
if(!req.headers.authorization){
res.status(403).json({
message: "Auth failed"
});
return;
}
const jwtToken = req.headers.authorization.replace('Bearer ', '');
aad.verify(jwtToken, null, function (err, result) {
if (result) {
next();
} else {
res.status(401).json({
message: "Auth failed"
});
}
});
};
config.spec.js
const request = require('supertest');
const sinon = require('sinon');
const app = require('../app');
const authorization = require('../security/authorization');
var agent;
describe('GET /api/names', () => {
before(() => {
ensureAuthenticatedSpy = sinon.stub(authorization, 'authorize');
ensureAuthenticatedSpy.callsArgWithAsync(2);
agent = require('supertest')
.agent(require('../app'));
});
it('should return a list of names', done => {
agent
.get('/api/config/buildPro/sites')
.expect(200)
.end((err, res) => {
if (err) return done(err);
done();
});
});
});
instead executes the actual authorization
This is exactly what is happening.
Note this code in server:
app.all('/api/*', authorization.authorize);
This resolves authorize function reference in this particular state of program and express will use this particular function (original one!) for rest of program.
This:
ensureAuthenticatedSpy = sinon.stub(authorization, 'authorize');
is called later and given that sinon has no power to change references to original authorize captured previously ... is no-op.
IOW, dependency injection in basic Javascript apps is not as simple as one might want.
To workaround, you can change original route in app.js:
app.all('/api/*', (req, res, next) => authorization.authorize(req, res, next));
Now, your closure would resolve authorization.authorize every-time it's called, enabling mocking/spying function you're interested in. However this is solution is far from elegant.
Using Mocha/Chai for REST API unit testing, I need to be able to mock req.session.someKey for a few of the end points. How can I go about mocking req.session?
I'm working on writing REST API unit tests for a NodeJS Express app that utilizes express-session. Some of these endpoints require the use of data stored in req.session.someKey, the endpoint is setup to return a 400 if req.session.someKey is undefined so I need to be able to mock it in order for the test to complete successfully.
Example code:
router.get('/api/fileSystems', utilities.apiAuth, (req, res) => {
let customer = req.session.customer;
let route = (customer === 'NONE') ? undefined : customer;
if(route == undefined){
res.status(400).send('Can't have customer of undefined');
} else {
let requestOptions = setRequestOptions(route);
queryFileSystemInfo(requestOptions, (info) => {
res.status(200).send(info);
});
}
});
What I've tried:
describe('/GET /api/fileSystems', () => {
it('It should return information about the filesystem for a customer'), (done) => {
chai.request(server)
.get('api/fileSystems')
.set('customer', '146')
.end((err, res) => {
res.should.have.status(200);
done();
});
});
});
I attempted to use the .set() in order to set req.session but I believe that .set just sets the headers so I don't believe that I can update it that way unless I'm missing something.
In your express setup you usually plug in the session middleware like this
app.use(session(config))
instead you can put the session middleware in a handy accessible location, and make a wrapper for it, like this:
app.set('sessionMiddleware') = session(config)
app.use((...args) => app.get('sessionMiddleware')(...args)
Tests will need access to the express instance, you can do this by refactoring /app.js to export a function.
function app () {
const app = express()
// ... set up express
return app
}
// run app if module called from cli like `node app.js`
if (require.main === module) instance = app()
module.exports = app
Then in your test, you can overwrite app.sessionMiddleware
describe('/GET /api/fileSystems', () => {
it('It should return information about the filesystem for a customer'), (done) => {
app.set('sessionMiddleware') = (req, res, next) => {
req.session = mockSession // whatever you want here
next()
}
chai.request(server)
.get('api/fileSystems')
.set('customer', '146')
.end((err, res) => {
res.should.have.status(200);
done();
});
// then you can easily run assertions against your mock
chai.assert.equal(mockSession.value, value)
});
});
The other options I've seen on the net involve setting a cookie to match a session which is stored in the db, the problem with that approach is that you end up running into problems when the session in the db expires, so tests fail over time as fixtures become stale. With the approach outlined above you can work around that by setting expiries in the test.
mock-session is pretty use full to mock your session object
let mockSession = require('mock-session');
describe('/GET /api/fileSystems', () => {
it('It should return information about the filesystem for a customer'), (done) => {
let cookie = mockSession('my-session', 'my-secret', {"count":1}); // my-secret is you session secret key.
chai.request(server)
.get('api/fileSystems')
.set('cookie',[cookie])
.end((err, res) => {
res.should.have.status(200);
done();
});
});
});
For this project, I ended up having to set req.session.customer in our server.js file that has an app.use() call that uses a middleware function to set the current session. I was unable to actually find a package that directly mutates the req.session object at test time.