Jest & Supertest - Response is not populate - node.js

I have written a following test case using jest to test REST API:
test('Should return module for given module name' , async () =>{
const response = await request(app)
.get('/modules')
.set('Authorization', 'Bearer ' +User1.tokens[0].token)
.send('modulename','Shark')
.expect(200)
expect(response.body.name[0]).toBe('Shark')
Corresponding node js route is
var modulename = req.query.modulename;
console.log(modulename)
const modules = await LuaModule.findByName(modulename)
res.send(modules)
}
.expect(200) succeeds however expect(response.body.name).toBe('Shark') fails even though API returns JSON response having 'name' field with value as 'Shark'. Jest throws following error:
Expected: Shark
Received: undefined
Server-Side Route-handler:
router.get('/modules', auth, async(req, res) => {
var modulename = req.query.modulename;
console.log(modulename);
const modules = await LuaModule.findByName(modulename);
res.send(modules);
}
Any suggestion regarding this will be helpful.

As you're expecting a query parameter to be set in the request on the server side, you need to make sure it's set correctly by supertest:
const response = await request(app)
.get('/modules')
.set('Authorization', 'Bearer ' +User1.tokens[0].token)
.query({modulename: 'Shark'}); // instead of .send(...)
...
Also since you're returning an array (and not an object) you need to access the name property of the object at the first index as:
expect(response.body[0].name).toBe('Shark')

Related

How to fix an endpoint test that returns 404 status code rather than 200 using express, jest and supertest

My end goal is that I want to be able to create a test that satisfies the following statement:
verify that requests to valid URLs return a 200 HTTP status code
A valid URL for example would be /about-page or /jobs, basically any directory that I add in my content folder that contains a file with the extension /index.md.
This is my code so far:
app.js
const readFilePromise = util.promisify(fs.readFile)
app.get('/*', (req, res) => {
readFilePromise(path.join(__dirname, 'content', req.url) + '/index.md', 'utf8')
.then(data => {
convertData(data, res)
})
.catch(err => {
res.status(404).send('Page doesn\'t exist!')
})
})
const convertData = (data, res) => {
const convertedData = md.render(data)
readFilePromise(path.join(__dirname, '/template.html'), 'utf8')
.then(data => {
data = data.replace(/\{\{content\}\}/, convertedData)
res.send(data)
})
.catch(err => {
console.log(err)
})
}
app.listen(3000)
module.exports = app
After reading this article, it mentions that
Requests are asynchronous, which means you must be able to conduct asynchronous tests.
So I wrote the following test:
app.test.js
const app = require('./app.js')
const request = supertest(app)
const supertest = require('supertest')
it('Gets the test endpoint and returns a 200 status', async done => {
const res = await request.get('/*')
expect(res.status).toBe(200)
done()
})
When I run the test, it fails with a 404 status, rather than returning a 200 status. I thought this might be due to my app.js not being in the async/await style, so I changed app.js to:
const readFilePromise = util.promisify(fs.readFile)
app.get('/*', async (req, res) => {
try {
await readFilePromise(path.join(__dirname, 'content', req.url) + '/index.md', 'utf8')
} catch (err) {
res.status(404).send('Page doesn\'t exist!')
}
try {
const convertedData = md.render(data)
await readFilePromise(path.join(__dirname, '/template.html'), 'utf8')
data = data.replace(/\{\{content\}\}/, convertedData)
res.send(data)
} catch (err) {
console.log(err)
}
})
app.listen(3000)
module.exports = app
I tried running the test again, but it still fails with a 404. I think my set up within app.test.js is wrong, but I'm not sure exactly what, as I've tried using the various set ups as the article. How would I fix this?
Separately, when I try going to a URL using the async/await style in app.js, I get a ReferenceError: data is not defined error, but I'm not sure how to define data in the async/await format.
I explained here how to set up app for the test environment: supertest not found error testing express endpoint
You did not mention how you set the database environment, make sure your database is not empty. Then make your get request. but just checking status for get request is not enough because if your db is empty you will still get 200.
const response = await request(app).get("/route").send().expect(200);
expect(response.body.length).toBeGreaterThan(0)
Better approach would be connect to a different database, post your data first and then check the response
const response = await request(app).get("/api/tickets").send().expect(200);
expect(response.body.length).toEqual(2); // if you post two items
Also before you every test make sure you start with empty database inside beforeEach()

How to evaluate API response from server and act accordingly at client side using Fetch() and Node.js

I fetch data at server side and push the result to global variable and then send that global variable to client with app.post method using Express.js. My problem is that client fetches the global variable too soon without the data received from the API first. How can I evaluate the response so that client would wait the global variable to reveive data first before displaying anything.
Server side, code looks something like this:
let sharpe = ''
app.post('/api', async(req, res, next) => {
console.log('I got a request!')
thisOne = req.body.stock1
thisOne2 = req.body.stock2
var result = await setup();
res.json({
status: 'success',
stocks: sharpe
});
})
Sharpe is the global variable storing the response from multiple API calls and is the one that should be sent back to client. Client side code is this:
const sendData = async (event) => {
event.preventDefault();
var stock1 = document.getElementById('weight1').value
var stock2 = document.getElementById('weight2').value
const data = {stock1, stock2};
const options = {
method: 'POST',
body: JSON.stringify(data),
headers: {'Content-Type': 'application/json' }
}
fetch('/api', options).then(res => res.json()).then(res => {
console.log(res.stocks);
})
}
As a result SendData() function fetches the sharpe variable that is empty at the moment. How can I adjust client side code or server side code that the client waits for a correct response? Thanks.
One solution would be to store the API results to database and client would fetch ready made datastream but is there more straightforward solution?
To wait for your API Server to set the sharpe Parameter, it needs to be awaited, which you already did. It depends on the setup function (for example setup()) which fills the sharpe parameter. It has to return a promise, which is resolved once sharpe is filled with the data.
let sharpe = ''
async setup() {
return new Promise((resolve, reject) => {
sharpe = 'test';
// set value for sharpe
resolve()
})
}
app.post('/api', async(req, res, next) => {
console.log('I got a request!')
thisOne = req.body.stock1
thisOne2 = req.body.stock2
var result = await setup();
res.json({
status: 'success',
stocks: sharpe
});
})
Eventually it starded working when I pushed all the API calls in the app.post middleware at the server side using promise chaining. The initial problem remains a mystery.

Issues mocking return value of Axios post call for unit test

Trying to mock an Axios call to unit test a token response from our identity software. Axios is not getting called at all and because of that my return is always undefined.
I've tried changing up Axios call to axios.post and changing the way I've mocked this more times then I can count. I don't believe like I should have to install another mocking framework just for Axios to mock this one function.
Implementation:
async getAuthToken() {
const oauthUrl = process.env.OAUTHURL;
const oauthAudience = process.env.OAUTHAudience;
const oauthUsername = process.env.OAUTHUSERNAME;
const oauthPassword = process.env.OAUTHPASSWORD;
let urlForAuth = oauthUrl
urlForAuth = urlForAuth + '/as/token.oauth2?';
urlForAuth = urlForAuth + 'grant_type=client_credentials';
urlForAuth = urlForAuth + '&aud=' + oauthAudience + '/';
urlForAuth = urlForAuth + '&scope=' + oauthAudience + '/.read';
const options = {
method: 'POST',
url: urlForAuth,
headers: {
'Authorization': "Basic " + Buffer.from(oauthUsername + ":" + oauthPassword).toString("base64")
},
responseType: 'json'
};
try{
let response = await axios(options);
return response.data.access_token;
}
catch(e){
console.log(e);
throw e;
}
}
Test Case:
test('token Is Returned', async () => {
expect.hasAssertions();
let Response = `{
"access_token": "thisisatoken",
"token_type": "Bearer",
"expires_in": 3599
}`;
axios = jest.fn(() => Promise.resolve());
axios.mockImplementationOnce(() =>
Promise.resolve({
data: Response
})
);
let response = await AuthService.getAuthToken();
expect(axios).toHaveBeenCalledTimes(1);
expect(response).toEqual("thisisatoken");
});
The error I am getting is
Expected mock function to have been called one time, but it was called zero times.
When I debug the data element on the response contains the following:
data:"Copyright (c) 2019 Next Small Things\n"
That is no where in my code. help.
You cannot mock things this way. Actually you are mocking axios only for your test's code but not for component under test that import's axios on its own.
You need to mock module properly and you have actually plenty of options:
provide ready-to-use mock in __mocks__ folder
call jest.mock('axios') to get autogenerated mock(each exported member will become jest.fn automatically)
provide factory for mock jest.mock('axios', () => { .... return object like it all are exported from file ... })
Also you need to import axios into your test to access it:
import axios from 'axios';
jest.mock('axios');
test('token Is Returned', async () => {
expect.hasAssertions();
let Response = `{
"access_token": "thisisatoken",
"token_type": "Bearer",
"expires_in": 3599
}`;
axios.mockReturnValue(() =>
Promise.resolve({
data: Response
})
);
let response = await AuthService.getAuthToken();
expect(axios).toHaveBeenCalledTimes(1);
expect(response).toEqual("thisisatoken");
});
Beware of few things:
jest.mock is hoisted to the top even if it's declared somewhere really deep in test's code. So it's better to place all jest.mock at the top of the file - because it anyway works this way - and this way another developer would not be confused if it's mocked or not.
if using __mocks__ folder with auto mock you better inject jest.fn() in advance - most times you will like to check if part of mock has been called and with what arguments
jest.mock cannot refer to any sibling variable except those one with name starting from mock.... See Service mocked with Jest causes "The module factory of jest.mock() is not allowed to reference any out-of-scope variables" error with really good explanation.
it'd be hard(near to impossible) to unmock module partially. so consider your test either mock some module or does not mock at all to test it.

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.

Using Superagent/Supertest with Express app in Mocha tests

I am attempting to write tests for the REST API that I am developing, using Mocha. I discovered Superagent which lets me make HTTP requests. I am using an Express app, which I pass to Superagent, but I get strange errors about Mongoose when trying to run these tests with the Express app passed in this way. Here is my code for the tests:
var
// Node modules
mongoose = require('mongoose')
, async = require('async')
, should = require('should')
, request = require('superagent')
, app = require('../../app_mocked')
, Link = mongoose.model('Link')
request = request(app)
describe('Links resource', function () {
var userId = '50dc81654dca01006b000005'
, linkId
, sampleLink = {
'uri': 'http://test.com/',
'meta': {
'title': 'Test',
'desc': 'Test link desc'
},
'shares': [{
'uid': userId,
'date': new Date(),
'message': 'Test link message'
}]
}
it('POST /api/users/:id/links', function (done) {
request(app).post('/api/users/' + userId + '/links')
.send(sampleLink)
.end(function (err, res) {
res.should.have.status(200)
res.body.should.have.property('id')
linkId = res.body.id
done()
})
})
it('GET /api/users/:id/links', function (done) {
request(app).get('/api/users/50dc81654dca01006b000005/links')
.end(function (err, res) {
res.should.have.status(200)
res.body.should.have.lengthOf(1)
done()
})
})
})
The error I get is this:
1) Links resource POST /api/users/:id/links:
TypeError: Cannot call method 'update' of null
at MongoStore.MONGOSTORE.set (/Users/Oliver/Development/Personal/Reader/node_modules/connect-mongodb/lib/connect-mongodb.js:146:15)
at Session.save (/Users/Oliver/Development/Personal/Reader/node_modules/express/node_modules/connect/lib/middleware/session/session.js:63:25)
at ServerResponse.res.end (/Users/Oliver/Development/Personal/Reader/node_modules/express/node_modules/connect/lib/middleware/session.js:280:19)
at ServerResponse.res.send (/Users/Oliver/Development/Personal/Reader/node_modules/express/lib/response.js:149:8)
at ServerResponse.res.json (/Users/Oliver/Development/Personal/Reader/node_modules/express/lib/response.js:191:15)
at ServerResponse.res.send (/Users/Oliver/Development/Personal/Reader/node_modules/express/lib/response.js:117:21)
at Promise.exports.create (/Users/Oliver/Development/Personal/Reader/server/resources/links.js:29:9)
However, this error only appears sometimes. 1/5 times, the tests will pass with no problem. This makes me think that the tests are sometimes running before app has fully loaded.
Alternatively, if I run the app in a separate session and just pass the URL to request, like below, then it works every time:
request = request('http://localhost:3000')
Is this the reason why? If so, how can I only run the tests once app has fully loaded?
Turns out that I had to specify a before test to wait for the MongoDB connection to open before running the tests.
before(function (done) {
mongoose.connection.on('open', done)
})

Resources