Related
I have a web application in Vue which talks with a REST API written in NodeJS. In the web application, user can submit a file aside with it's information. The web passes this information to the backend using the REST API. The code in the web side:
var formData = new FormData();
formData.append("number", this.number);
formData.append("file", this.file);
// More fields inside the Form Data object
this.$http.post('http://localhost:8081/api/new',formData,{emulateJSON: true},{
header:{ "Content-Type":"multipart/form-data" },
}).then(function(response) {
if (response.status == 200) {
this.handleClose();
} else {
console.log("Failed to submit new report");
}
}).catch((err) => {
console.log("Failed to submit new report");
});
In the backend side, I have:
const router = require('express').Router();
router.post('/new', function(request, response) {
console.log("New Report");
console.log(request.body);
var info = {
"number": request.body.number,
// More fields
};
var file = request.files.file;
// More Code
});
It works great but now I want to create tests for the REST API. I have the following code:
const chai = require('chai');
const nock = require('nock');
const FormData = require('form-data');
const request = require('supertest');
run_new_report_test: function(server) {
describe('Test: New report check', function() {
it('should return status 200', function(done) {
var formData = new FormData();
formData.append("number", "55555");
// More fields
request(server)
.post('/api/new')
.set("Content-Type", "multipart/form-data")
.attach(formData)
.expect(200)
.end(function(err, res) {
expect(err).to.equal(null);
expect(res.body).to.be.an('object').that.is.not.empty;
expect(res.body.message).to.be.equal('success');
done();
});
});
});
},
But when I run the test using mocha, I get:
New Report
undefined
For some reason request.body is undefined. I don't understand where the FormData fields are located in request. Why it works when I make a request from the web but not from the test?
instead of form-data, you need to use .field(for req.body), and .attach(for file) methods. check the docs: https://visionmedia.github.io/superagent/#multipart-requests
also, you can mock file with buffer, you need to add some file info as well, try this:
describe('Test: New report check', function() {
it('should return status 200', function(done) {
const testFile = 'test file';
request(server)
.post('/api/new')
// add each filed for req.body
.field('number', '99999')
// mock file
.attach('file', Buffer.from(testFile, 'utf-8'), {
// add file info accordingly
filename: 'file.txt',
contentType: 'text/plain',
knownLength: testFile.length
})
.expect(200)
.end(function(err, res) {
expect(err).to.equal(null);
expect(res.body).to.be.an('object').that.is.not.empty;
expect(res.body.message).to.be.equal('success');
done();
});
});
});
You are pass the data in params frontend side and you try to access in body row data in backend side that's why undefined.
try this
router.post('/new', function(request, response) {
console.log("New Report");
console.log(request.param);
var info = {
"number": request.param.number,
// More fields
};
var file = request.files.file;
// More Code
});
I wrote the unit tests:
var app = require('../server');
var chai = require('chai');
var supertest = require("supertest")(app);
var GoogleUrl = require('google-url');
var config = require('../config');
var expect = chai.expect;
describe('Urls Tests', function () {
var url = {
author : 'Alexey',
description : 'grrggr',
full_url : 'https://github.com',
date : '30-06-2017',
time : '18:21:27',
count_click : 0,
list_tags : [
'Sport',
'Football'
]
};
var token;
beforeEach(function (done) {
agent
.post('http://localhost:8000/auth/login')
.send({email: 'Keane95#yandex.ru', password: '123456'})
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.body.userData).to.have.property('token');
token = res.body.userData.token;
done();
});
});
it('should create a url', function(done) {
var googleUrl = new GoogleUrl({
'key': config.get('google_key')
});
googleUrl.shorten(url.full_url, function (err, shortUrl) {
url.short_url = shortUrl;
supertest
.post('/urls/create')
.send(url)
.expect(401)
.end(function (err, res) {
if (err) return done(err);
expect(res.body.author).to.equal('Alexey');
url = res.body;
done();
});
});
});
it('should modify a url by id', function(done) {
url.description = 'Good description';
url.list_tags.push('Liverpool');
supertest
.put('/urls/' + url._id)
.send(url)
.expect(401)
.end(function(err, res) {
if (err) return done(err);
expect(res.body.description).to.equal('Good description');
expect(res.body.list_tags[2]).to.equal('Liverpool');
done();
});
});
it('should modify a count of clicks', function(done) {
url.count_click++;
supertest
.put('/urls/' + url._id)
.send(url)
.expect(401)
.end(function(err, res) {
if (err) return done(err);
expect(res.body).to.equal('Count of the click is updated');
done();
});
});
});
I run to execute the unit tests and get the errors:
I read the articles by unit tests.
First article: http://developmentnow.com/2015/02/05/make-your-node-js-api-bulletproof-how-to-test-with-mocha-chai-and-supertest/
Second article: https://www.codementor.io/olatundegaruba/integration-testing-supertest-mocha-chai-6zbh6sefz
I don't understand why I get these errors. Please, help me. I think that I made little error, but since I cannot fint it.
UPDATED
I added route:
var express = require('express');
var GoogleUrl = require('google-url');
var _ = require('lodash');
var token = require('../middlewares/token');
var Url = require('../models/url');
var config = require('../config');
var router = express();
router.post('/create', token.required, createShortUrl);
router.put('/count/:id', token.required, updateCountClick);
router.put('/:id', token.required, updateUrlById);
module.exports = router;
function createShortUrl(req, res) {
_.trim(req.body.list_tags);
var tags = _.split(req.body.list_tags, ',');
tags.splice(tags.length - 1, 1);
var date = returnDate();
var time = returnTime();
var googleUrl = new GoogleUrl({
'key': config.get('google_key')
});
googleUrl.shorten(req.body.full_url, function (err, shortUrl) {
if (err) {
res.status(500).json(err);
}
var url = new Url({
'author': req.payload.username,
'description': req.body.description,
'full_url': req.body.full_url,
'short_url': shortUrl,
'list_tags': tags,
'date': date,
'time': time
});
url.save(function (err, url) {
if (err) {
return res.status(500).json(err);
} else {
return res.status(200).json(url);
}
});
});
}
function updateCountClick(req, res) {
var count_click = req.body.count_click + 1;
Url.findOneAndUpdate({_id: req.params.id}, {$set: {count_click: count_click}}, {new: true}, function (err, url) {
if (err) {
return res.status(500).json(err);
}
if (url) {
return res.status(200).json('Count of the click is updated');
}
});
}
function updateUrlById(req, res) {
_.trim(req.body.list_tags);
var tags = _.split(req.body.list_tags, ',');
tags.splice(tags.length - 1, 1);
Url.findOneAndUpdate({_id: req.params.id}, {$set: {description: req.body.description, list_tags: tags}}, {new: true}, function (err, url) {
if (err) {
res.status(500).json(err);
}
if (url) {
res.status(200).json(url);
}
});
}
UPDATED 2
Authoziration was added:
var token;
beforeEach(function (done) {
agent
.post('http://localhost:8000/auth/login')
.send({email: 'Keane95#yandex.ru', password: '123456'})
.end(function (err, res) {
if (err) {
return done(err);
}
expect(res.body.userData).to.have.property('token');
token = res.body.userData.token;
done();
});
});
Also I updated code my unit-tests.
I can't see where in your code you send 401 and Url. So it seems that your test requests are getting rejected by token.required middleware with 401 status code (which means "unauthorized").
.send(url)
.expect(401) // why do you expect 401? You never send it inside your logic
So basically your test never hit actual code.
First of all, you do need to fake authorization to make token.required middleware happy.
Then expect 200 result
.send(url)
.expect(200) // normal execution flow of createShortUrl results in 200
.end(/* rest of your test logic */)
I want to be able to grab some response properties and throw them into a variable at times with SuperTest. How can I do this? I don't see the docs doing anything but assertions on the response.
for example I'd like to do something like this:
var statusCode = request(app).get(uri).header.statusCode;
I'd like to do something like this. Because sometimes I like to split out the asserts into seperate Mocha.js it() tests due to the fact I'm doing BDD and so the 'Thens' in this case are based on the expected response parts so each test is checking for a certain state coming back in a response.
for example I'd like to do this with supertest:
var response = request(app).get(uri);
it('status code returned is 204, function(){
response.status.should.be....you get the idea
};
it('data is a JSON object array', function(){
};
Here is an example how you can accomplish what you want:
server file app.js:
var express = require('express');
var app = express();
var port = 4040;
var items = [{name: 'iphone'}, {name: 'android'}];
app.get('/api/items', function(req, res) {
res.status(200).send({items: items});
});
app.listen(port, function() {
console.log('server up and running at %s:%s', app.hostname, port);
});
module.exports = app;
test.js:
var request = require('supertest');
var app = require('./app.js');
var assert = require('assert');
describe('Test API', function() {
it('should return 200 status code', function(done) {
request(app)
.get('/api/items')
.end(function(err, response) {
if (err) { return done(err); }
assert.equal(response.status, 200);
done();
});
});
it('should return an array object of items', function(done) {
request(app)
.get('/api/items')
.end(function(err, response) {
if (err) { return done(err); }
var items = response.body.items;
assert.equal(Array.isArray(items), true);
done();
});
});
it('should return a JSON string of items', function(done) {
request(app)
.get('/api/items')
.end(function(err, response) {
if (err) { return done(err); }
try {
JSON.parse(response.text);
done();
} catch(e) {
done(e);
}
});
});
});
You can see some examples here on the superagent github library since supertest is based on superagent library.
I'm trying to test the get function:
exports.get = function(req, res) {
Subscriptions
.find(req.params.id)
.success(function(subscription) {
if (subscription) {
res.json({message: "Success"}, 200);
} else {
res.json({message: "Not found"}, 404);
}
})
.error(function(error) {
res.json({message: "Internal server error"}, 500);
});
};
Specifically, I don't really care if it hits the database, I only want to test the scenarios where the success and error events occur. I'm using sequelize.js as my orm to handle the database. I've gotten a test up and running, but its a bit nasty, with the timeout. Is there a better way of doing this? Here's the test I've written so far:
var express = require('express')
, sinon = require('sinon')
, subscription = require('app/controllers/subscriptions')
, Subscriptions = require('app/models/subscriptions')
;
describe('subscription controller', function() {
beforeEach(function() {
this.mockResponse = sinon.mock(express.response);
});
afterEach(function() {
this.mockResponse.restore();
});
describe('GET /subscriptions/:id', function() {
it('should return a json response', function(done) {
var request = {
params: {
id: 'identifier'
}
};
var expectedResponse = {
subscriptions_uri : "/subscription/identifier"
};
this.mockResponse
.expects('json')
.once()
.withArgs(expectedResponse);
subscription.get(request, express.response);
setTimeout(function() {
done();
}, 500);
});
});
});
I decided to use the supertest library, which made testing my controller incredibly easy:
var express = require('express')
, subscription = require('app/controllers/subscriptions')
, request = require('supertest')
, app = express()
;
describe('subscription controller', function() {
describe('GET /subscriptions/:id', function() {
it('should return a json response', function(done) {
var expectedBody = {
subscriptions_uri : "/subscription/identifier"
};
request(app)
.get('/subscriptions/identifier')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(expectedBody)
.expect(200, done);
});
});
});
I'm using Passport.js for authentication (local strategy) and testing with Mocha and Supertest.
How can I create a session and make authenticated requests with Supertest?
As zeMirco points out, the underlying superagent module supports sessions, automatically maintaining cookies for you. However, it is possible to use the superagent.agent() functionality from supertest, through an undocumented feature.
Simply use require('supertest').agent('url') instead of require('supertest')('url'):
var request = require('supertest');
var server = request.agent('http://localhost:3000');
describe('GET /api/getDir', function(){
it('login', loginUser());
it('uri that requires user to be logged in', function(done){
server
.get('/api/getDir')
.expect(200)
.end(function(err, res){
if (err) return done(err);
console.log(res.body);
done()
});
});
});
function loginUser() {
return function(done) {
server
.post('/login')
.send({ username: 'admin', password: 'admin' })
.expect(302)
.expect('Location', '/')
.end(onResponse);
function onResponse(err, res) {
if (err) return done(err);
return done();
}
};
};
You should use superagent for that. It is lower level module and used by supertest. Take a look at the section Persisting an agent:
var request = require('superagent');
var user1 = request.agent();
user1
.post('http://localhost:4000/signin')
.send({ user: 'hunter#hunterloftis.com', password: 'password' })
.end(function(err, res) {
// user1 will manage its own cookies
// res.redirects contains an Array of redirects
});
Now you can use user1 to make authenticated requests.
Try this,
var request=require('supertest');
var cookie;
request(app)
.post('/login')
.send({ email: "user#gluck.com", password:'password' })
.end(function(err,res){
res.should.have.status(200);
cookie = res.headers['set-cookie'];
done();
});
//
// and use the cookie on the next request
request(app)
.get('/v1/your/path')
.set('cookie', cookie)
.end(function(err,res){
res.should.have.status(200);
done();
});
As an addendum to Andy's answer, in order to have Supertest startup your server for you, you can do it like this:
var request = require('supertest');
/**
* `../server` should point to your main server bootstrap file,
* which has your express app exported. For example:
*
* var app = express();
* module.exports = app;
*/
var server = require('../server');
// Using request.agent() is the key
var agent = request.agent(server);
describe('Sessions', function() {
it('Should create a session', function(done) {
agent.post('/api/session')
.send({ username: 'user', password: 'pass' })
.end(function(err, res) {
expect(req.status).to.equal(201);
done();
});
});
it('Should return the current session', function(done) {
agent.get('/api/session').end(function(err, res) {
expect(req.status).to.equal(200);
done();
});
});
});
I'm sorry, but neither of suggested solutions doesn't work for me.
With supertest.agent() I can't use the app instance, I'm required to run the server beforehand and specify the http://127.0.0.1:port and moreover I can't use supertest's expectations (assertions), I can't use the supertest-as-promised lib and so on...
The cookies case won't work for me at all.
So, my solution is:
If you are using Passport.js, it utilizes the "Bearer token" mechanism and you can use the following examples in your specs:
var request = require('supertest');
var should = require('should');
var app = require('../server/app.js'); // your server.js file
describe('Some auth-required API', function () {
var token;
before(function (done) {
request(app)
.post('/auth/local')
.send({
email: 'test#example.com',
password: 'the secret'
})
.end(function (err, res) {
if (err) {
return done(err);
}
res.body.should.to.have.property('token');
token = res.body.token;
done();
});
});
it('should respond with status code 200 and so on...', function (done) {
request(app)
.get('/api/v2/blah-blah')
.set('authorization', 'Bearer ' + token) // 1) using the authorization header
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) {
return done(err);
}
// some `res.body` assertions...
done();
});
});
it('should respond with status code 200 and so on...', function (done) {
request(app)
.get('/api/v2/blah-blah')
.query({access_token: token}) // 2) using the query string
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) {
return done(err);
}
// some `res.body` assertions...
done();
});
});
});
You may want to have a helper function to authenticate users:
test/auth-helper.js
'use strict';
var request = require('supertest');
var app = require('app.js');
/**
* Authenticate a test user.
*
* #param {User} user
* #param {function(err:Error, token:String)} callback
*/
exports.authenticate = function (user, callback) {
request(app)
.post('/auth/local')
.send({
email: user.email,
password: user.password
})
.end(function (err, res) {
if (err) {
return callback(err);
}
callback(null, res.body.token);
});
};
Have a productive day!
I'm going to assume that you're using the CookieSession middleware.
As grub mentioned, your goal is to get a cookie value to pass to your request. However, for whatever reason (at least in my testing), supertest won't fire 2 requests in the same test. So, we have to reverse engineer how to get the right cookie value. First, you'll need to require the modules for constructing your cookie:
var Cookie = require("express/node_modules/connect/lib/middleware/session/cookie")
, cookieSignature = require("express/node_modules/cookie-signature")
Yes, that's ugly. I put those at the top of my test file.
Next, we need to construct the cookie value. I put this into a beforeEach for the tests that would require an authenticated user:
var cookie = new Cookie()
, session = {
passport: {
user: Test.user.id
}
}
var val = "j:" + JSON.stringify(session)
val = 's:' + cookieSignature.sign(val, App.config.cookieSecret)
Test.cookie = cookie.serialize("session",val)
Test.user.id was previously defined in the portion of my beforeEach chain that defined the user I was going to "login". The structure of session is how Passport (at least currently) inserts the current user information into your session.
The var val lines with "j:" and "s:" are ripped out of the Connect CookieSession middleware that Passport will fallback on if you're using cookie-based sessions. Lastly, we serialize the cookie. I put "session" in there, because that's how I configured my cookie session middleware. Also, App.config.cookieSecret is defined elsewhere, and it must be the secret that you pass to your Express/Connect CookieSession middleware. I stash it into Test.cookie so that I can access it later.
Now, in the actual test, you need to use that cookie. For example, I have the following test:
it("should logout a user", function(done) {
r = request(App.app)
.del(App.Test.versionedPath("/logout"))
.set("cookie", Test.cookie)
// ... other sets and expectations and your .end
}
Notice the call to set with "cookie" and Test.cookie. That will cause the request to use the cookie we constructed.
And now you've faked your app into thinking that user is logged in, and you don't have to keep an actual server running.
Here is a neat approach which has the added benefit of being reusable.
const chai = require("chai")
const chaiHttp = require("chai-http")
const request = require("supertest")
const app = require("../api/app.js")
const should = chai.should()
chai.use(chaiHttp)
describe("a mocha test for an expressjs mongoose setup", () => {
// A reusable function to wrap your tests requiring auth.
const signUpThenLogIn = (credentials, testCallBack) => {
// Signs up...
chai
.request(app)
.post("/auth/wizard/signup")
.send({
name: "Wizard",
...credentials,
})
.set("Content-Type", "application/json")
.set("Accept", "application/json")
.end((err, res) => {
// ...then Logs in...
chai
.request(app)
.post("/auth/wizard/login")
.send(credentials)
.set("Content-Type", "application/json")
.set("Accept", "application/json")
.end((err, res) => {
should.not.exist(err)
res.should.have.status(200)
res.body.token.should.include("Bearer ")
// ...then passes the token back into the test
// callBack function.
testCallBack(res.body.token)
})
})
}
it.only("flipping works", done => {
// "Wrap" our test in the signUpThenLogIn function.
signUpLogIn(
// The credential parameter.
{
username: "wizard",
password: "youSHALLpass",
},
// The test wrapped in a callback function which expects
/// the token passed back from when signUpLogIn is done.
token => {
// Now we can use this token to run a test...
/// e.g. create an apprentice.
chai
.request(app)
.post("/apprentice")
.send({ name: "Apprentice 20, innit" })
// Using the token to auth!
.set("Authorization", token)
.end((err, res) => {
should.not.exist(err)
res.should.have.status(201)
// Yep. apprentice created using the token.
res.body.name.should.be.equal("Apprentice 20, innit")
done()
})
}
)
})
})
BONUS MATERIAL
To make it even more reusable, put the function into a file called "myMochaSuite.js" which you can replace "describe" with when testing your api server. Be a wizard and put all your before/after stuff in this "suite". e.g.:
// tests/myMochaSuite.js
module.exports = (testDescription, testsCallBack) => {
describe(testDescription, () => {
const signUpThenLogIn = (credentials, testCallBack) => {
// The signUpThenLogIn function from above
}
before(async () => {
//before stuff like setting up the app and mongoose server.
})
beforeEach(async () => {
//beforeEach stuff clearing out the db
})
after(async () => {
//after stuff like shutting down the app and mongoose server.
})
// IMPORTANT: We pass signUpLogIn back through "testsCallBack" function.
testsCallBack(signUpThenLogIn)
})
}
// tests/my.api.test.js
// chai, supertest, etc, imports +
const myMochaSuite = require("./myMochaSuite")
// NB: signUpThenLogIn coming back into the tests.
myMochaSuite("my test description", signUpThenLogIn => {
it("just works baby", done => {
signUpThenLogIn(
{username: "wizard", password: "youSHALLpass"},
token => {
chai
.request(app)
.get("/apprentices/20")
// Using the incoming token passed when signUpThenLogIn callsback.
.set("Authorization", token)
.end((err, res) => {
res.body.name.equals("Apprentice 20, innit")
done()
})
}
)
})
})
Now you have a even more reusable suite "wrapper" for all your tests, leaving them uncluttered.
GraphQl full Example:
const adminLogin = async (agent) => {
const userAdmin = await User.findOne({rol:"admin"}).exec();
if(!userAdmin) return new Promise.reject('Admin not found')
return agent.post('/graphql').send({
query: ` mutation { ${loginQuery(userAdmin.email)} }`
})//.end((err, {body:{data}}) => {})
}
test("Login Admin", async (done) => {
const agent = request.agent(app);
await adminLogin(agent);
agent
.post("/graphql")
.send({query: `{ getGuests { ${GuestInput.join(' ')} } }`})
.set("Accept", "application/json")
.expect("Content-Type", /json/)
.expect(200)
.end((err, {body:{data}}) => {
if (err) return done(err);
expect(data).toBeInstanceOf(Object);
const {getGuests} = data;
expect(getGuests).toBeInstanceOf(Array);
getGuests.map(user => GuestInput.map(checkFields(user)))
done();
});
})