I have some code in an Express route which talks to AWS Cognito and am having trouble working out how to mock it in tests.
cognitoExpress.validate(accessTokenFromClient, (err, response) => {
if (err) return res.status(401).json({ error: err });
res.json({ data: `Hello ${response.username}!` });
});
Then in my test I want to say cognitoExpress.validate should be called once and return {username: 'test user'} so that it doesnt hit the network and doesnt actually call AWS Cognito
it('It should returns 200 with a valid token', async done => {
const { cognitoExpress } = require('../helpers/cognitoExpress');
// I have tried
jest.mock('../helpers/cognitoExpress');
// and this
jest.mock('../helpers/cognitoExpress', () => ({
validate: jest.fn()
}));
const token = 'sfsfdsfsdfsd';
const response = await request.get('/').set('Authorization', token);
expect(cognitoExpress.validate).toHaveBeenCalledWith(token);
expect(response.body).toEqual({ data: 'Hello test user' });
done();
});
Thanks in advance....
let spyInstance = undefined;
beforeAll(() => {
spyInstance = jest.spyOn(cognitoExpress.prototype, "validate").mockImplementation(() => {
// Replace the body of 'validate' here, ensure it sets
// response body to {username: 'test user'} without calling AWS
...
});
});
afterAll(() => {
expect(spyInstance).toBeDefined();
expect(spyInstance).toHaveBeenCalledTimes(1);
jest.restoreAllMocks();
});
it("It should call mocked cognitoExpress.validate once", async done => {
...
});
A similar and working test in my project. Instead of cognitoExpress.validate it mocks and tests SampleModel.getData
Create file ../helpers/__mocks__/cognitoExpress.js with mocked function you want to use. It is essential to call the folder __mocks__. You can modify a functions and return any data you want.
example
module.exports = {
validate: () => { username: 'test user' }
}
Now you can use jest.mock('../helpers/cognitoExpress'), but I recommend you to place it to some global or test setup file, not to separate tests.
Jest Manual Mocks
Related
I have a request that has an internal dependency to a Facebook graph objects that performs another request against the FB graph API.
I'm wondering if it is possible to use sinon to mock the graph object so that it wouldn't actually perform a request in a test but would execute the callback function with a value that I provide in the test instead.
server.post("/facebookLogin", function(req, res) {
graph.setAccessToken(req.body.fbtoken);
graph.get("me?fields=email", function(err, obj) {
if (!err) {
var email = obj.email;
checkUserAlreadyRegistered(email, function(user) {
if (user) {
return res.send(200, {user:user, token: decorateToken(user.id)});
} else {
return res.send(404);
}
});
} else {
return res.send(500);
}
});
});
I had the exact same issue, and digging into the fbgraph source code I found out that even though it's using "graphql", internally is a network request with request so you can easily intercept it with nock:
// https://github.com/criso/fbgraph/blob/master/lib/graph.js#L34 <-- fb graph url
const fbMock = nock('https://graph.facebook.com/v4.0/')
.get('/me')
.query(true)
.reply(200, {
id: '123123',
name: 'fb username',
email: 'user#fb.com'
})
it('should not call fb"', (done) => {
chai.request(server)
.post('/facebookLogin')
.send({ fbtoken: 'token_fb' })
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(200)
expect(fbMock).to.have.been.requested
done()
})
}
note: the /v4.0/ part could be different depending on your configuration but the default value is 2.9 so be sure to use the same one you set with the setVersion method
I am attempting to unit test my authentication middleware for Express. The middleware is quite simple and can be viewed below in its entirety:
const admin = require('./../config/firebase/firebase');
// Models - User
const User = require('./../models/user');
const auth = async (req, res, next) => {
try {
// The Authorization Bearer Token sent in the header of the request needs to be decoded.
const token = req.header('Authorization').replace('Bearer ', '');
const decoded = await admin.auth().verifyIdToken(token);
// Finding that user in the database by their Firebase UID.
const user = await User.findOne({ _id: decoded.uid });
// If that user does not exist, we'll throw an error.
if (!user) {
throw new Error();
}
// Making the user accessible to the endpoint.
req.user = user;
// Proceed
next();
} catch (e) {
// HTTP 404 Unauthorized Response Status
res.status(401).send({ error: 'Please authenticate.' });
}
}
module.exports = auth;
Since the Firebase Admin SDK returns an object that contains the user's UID as a property, for the purpose of my tests, I create a "fake token" which is just an object with a UID property. I then mock the Admin SDK such that it returns whatever was passed in, as so:
module.exports = {
auth() {
return this;
},
verifyIdToken(token) {
return JSON.parse(token);
},
initializeApp(app) {
},
credential: {
cert() {
}
}
}
Since the auth middleware expects to find a user in the test database, I have to configure that as Jest Setup in the beforeAll hook:
const userOneToken = JSON.stringify({ uid: 'example UID' });
const userOne = {
_id: 'example UID',
// ...
};
beforeAll(async () => {
await User.deleteMany();
await User.save(userOne);
app.use(auth).get('/', (req, res) => res.send());
});
This means that the middleware will always be able to get a UID in return, which can be used to find a test user in the test database.
The test suite itself, after importing my Express Application, is quite simple, with just three tests:
const auth = require('./../../src/middleware/auth');
describe('Express Auth Middleware', () => {
test('Should return 401 with an invalid token', async () => {
await request(app)
.get('/')
.set('Authorization', 'Bearer 123')
.send()
.expect(401);
});
test('Should return 401 without an Authorization Header', async () => {
await request(app)
.get('/')
.send()
.expect(401);
});
test('Should return 200 with a valid token', async () => {
await request(app)
.get('/')
.set('Authorization', `Bearer ${userOneToken}`)
.send()
.expect(200);
});
});
It appears, however, that the tests are leaking memory (apparent by calling with the --detectLeaks flag). Additionally, it seems Jest is also finding an open handle left behind by the last test. Running the suite with the --detectOpenHandles flag returns a TCPSERVERWRAP error on the get request of the last test.
Potential solutions were proposed in this GitHub issue, but none of them worked for me.
Any help solving this issue would be much appreciated, for all my test suites are leaking memory because they rely on Supertest. Thank you.
I need to test if my POST request to my endpoint works properly with a Jest test. I had the idea of first getting the count of my Services table (I'm using sequelize orm), then to send a new post request and to finally get the new count and compare if the old count + 1 will equal to the new count, if true then the POST request works just fine.
test('Create a valid Service', async (done) => {
const service = {
name: "cool",
description: "description"
};
await Service.count().then(async function (count) {
await request(app)
.post('/api/services')
.send(service)
.then(async () => {
await Service.count().then(function (newcount) {
expect(newcount).toBe(count + 1);
});
})
.catch(err => console.log(`Error ${err}`));
});
});
For me the test looks fine, but when I run it I get:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
Is something missing or is there even a better way to test a POST request? with Jest?
It is because you are not calling the done callback passed in jest callback function. It can be done like this.
test('Create a valid Service', async(done) => {
const service = {
name: "cool",
description: "description"
};
await Service.count().then(async function (count) {
await request(app)
.post('/api/services')
.send(service)
.then(async() => {
await Service.count().then(function (newcount) {
expect(newcount).toBe(count + 1);
// execute done callback here
done();
});
})
.catch(err => {
// write test for failure here
console.log(`Error ${err}`)
done()
});
});
});
You can also write this code in this way so that the readability can be improved and maximize the use of async/await.
test('Create a valid Service', async(done) => {
const service = {
name: "cool",
description: "description"
};
try {
const count = await Service.count();
await request(app).post('/api/services').send(service)
const newCount = await Service.count()
expect(newCount).toBe(count + 1);
done()
} catch (err) {
// write test for failure here
console.log(`Error ${err}`)
done()
}
});
By default Jest also resolves the promise in async/await case. We can achieve this without the callback function also
test('Create a valid Service', async() => {
const service = {
name: "cool",
description: "description"
};
try {
const count = await Service.count();
await request(app).post('/api/services').send(service)
const newCount = await Service.count()
expect(newCount).toBe(count + 1);
} catch (err) {
// write test for failure here
console.log(`Error ${err}`)
}
});
So I have a route with a database call in a separate file:
module.exports = (req, res) => {
const sql = `SELECT Topic FROM surveys WHERE ID="${req.params.id}"`;
model.connection.query(sql, (err, result) => {
if (err) res.status(500).send(err);
res.send(result);
});
};
In my test file, I create fake req and res objects to test different inputs:
const req = {
body: {},
params: {
id: '',
}
};
const res = {
message: '',
stat: 200,
send(arg) {
this.message = arg;
},
status(number) {
this.stat = number;
return this;
}
};
And call it in every test:
it('Should return status 200 if parameter is a number string', () => {
req.params.id = '1';
getTopic(req, res);
chai.expect(res.stat).to.equal(200);
});
My question is, how can I test it asynchronously, without affecting my route?
I've seen Mocha documentation explaining this, but they require a callback in a function.
it('Should return status 200 if parameter is a number string', (done) => {
req.params.id = '1';
getTopic(req, res); // Cannot give any callback
chai.expect(res.stat).to.equal(200);
done(); //Doesn't work
});
I also tried using async/await, but it didn't work (I lack knowledge of how to use it, maybe that's why)
it('Should return status 200 if parameter is a number string', async () => {
req.params.id = '1';
await getTopic(req, res);
chai.expect(res.stat).to.equal(200);
});
And I've seen this question, and didn't help at all: unit testing express route with async callback
Here's the function I'm writing tests for:
ensureUserDoesNotExist(request, response, next) {
this.User.findOne({ where: { email: request.body.email }})
.then(user => {
if (user) {
response.sendStatus(403);
} else {
next();
}
});
}
And here's the test that I cannot get to pass:
it('should return a 403 if a matching user is found', () => {
mockRequest.body.email = 'test#email.com';
userController.User.findOne.resolves(true); // This is a previously created sinon stub
userController.ensureUserDoesNotExist(mockRequest, mockResponse, mockNext);
assert(mockResponse.sendStatus.calledWith(403));
});
It fails, simply claiming that the stub isn't called (at all, for what it's worth).
I strongly suspect this is to do with the promise - or Sinon's interaction with it - but am having a complete mind-blank in trying to figure out exactly what. The code works as intended (or it did when I last looked before playing about with it). Can anyone help me out?
Your assertion is evaluated before the end request
You need to return the promise
ensureUserDoesNotExist(request, response, next) {
return this.User.findOne({ where: { email: request.body.email }})
.then(user => {
if (user) {
response.sendStatus(403);
} else {
next();
}
});
}
and assert in then clause
it('should return a 403 if a matching user is found', () => {
mockRequest.body.email = 'test#email.com';
userController.User.findOne.resolves(true); // This is a previously created sinon stub
userController.ensureUserDoesNotExist(mockRequest, mockResponse, mockNext).then(() => {
assert(mockResponse.sendStatus.calledWith(403));
});
});
The test also must return a promise to indicate an asynchronous test to Mocha. You can use the one returned by the then call:
it('should return a 403 if a matching user is found', () => {
mockRequest.body.email = 'test#email.com';
userController.User.findOne.resolves(true); // This is a previously created sinon stub
return userController.ensureUserDoesNotExist(mockRequest, mockResponse, mockNext).then(() => {
assert(mockResponse.sendStatus.calledWith(403));
});
});