Reputation: 27
I am currently writing tests for my nodejs app in mocha. My api calls require that I am logged in, so I wanted to create a wrapper test suite that creates a test user and then calls the actual test suite. Heres what the code looks like:
var request = require('supertest');
var config = require('../config/config');
var AdminUser = require('../models/Authmodel');
function configureAuth(test_suite) {
var url = "localhost:" + config.port;
var email = "[email protected]";
var password = "test_password";
var admin;
var token;
describe("Signup User", function() {
it("should signup new user", function(done) {
request(url)
.post('/auth/signup')
.send({
email: email,
password: password
})
.expect(200)
.end(function(){
done();
});
});
it("should login the user", function(done) {
request(url)
.post('/auth/login')
.send({
email: email,
password: password
})
.expect(200)
.end(function(err,res){
if(err)
throw(err);
res.body.should.have.property('token');
token = res.body.token;
done();
});
});
it("should retrieve admin document", function(done) {
AdminUser.findOne({email: email}, function(err, dbAdmin) {
if(err)
throw(err);
admin = dbAdmin;
done();
});
});
});
// Call the actual test suite, pass it the auth credentials.
describe("Test Suite", function() {
it("should run the test suite", function(done) {
// No matter what the timeout is set to it still exceeds it
this.timeout(5000);
test_suite({
email: email,
password: password,
token: token,
admin: admin
}, done);
});
});
describe("Clear Admins", function() {
it("should clear the admin table", function(done) {
AdminUser.remove({email: email}, function(err) {
if(err)
throw(err);
done();
});
});
});
};
module.exports = configureAuth;
And here is a test suite using the wrapper:
var request = require('supertest');
var config = require('../config/config');
// Wrapper that creates admin user to allow api calls
var ConfigureAuth = require('./ConfigureAuth');
// Test data
var templateForm = {...}
var submittedForm = {...}
ConfigureAuth(
function(credentials, exit) {
var url = "localhost:" + config.port;
var templateFormId = null;
describe("Form Templates", function() {
describe('POST /api/form/template', function(){
it('should save the template', function(done){
request(url)
.post('/api/form/template')
.query({email: credentials.email, token: credentials.token})
.send({
_admin_id: credentials.admin._id,
template: templateForm,
})
.end(function(err, res){
templateFormId = res.body._id;
res.body.should.have.property('_admin_id').and.be.equal(''+credentials.admin._id);
res.body.should.have.property('template').and.be.instanceof(Object);
done();
});
});
});
describe('GET /api/form/template/:id', function(){
it('Should respond with template data', function(done){
request(url)
.get('/api/form/template/' + templateFormId)
.query({email: credentials.email, token: credentials.token})
.end(function(err, res){
...
done();
});
});
});
describe('GET /api/form/template/company/:id', function(){
it('Should respond with company template data', function(done){
request(url)
.get('/api/form/template/company/' + credentials.admin._id)
.query({email: credentials.email, token: credentials.token})
.end(function(err, res){
...
done();
});
});
});
describe('DELETE /api/form/template/:template_id', function(){
it('Should delete the template data', function(done){
request(url)
.delete('/api/form/template/' + templateFormId)
.query({email: credentials.email, token: credentials.token})
.end(function(err, res){
...
done();
});
});
});
});
describe("Submitted Forms", function() {
describe('POST /api/form/patient', function(){
it('should save submitted form', function(done){
request(url)
.post('/api/form/patient')
.query({email: credentials.email, token: credentials.token})
.send({
_admin_id: credentials.admin._id,
form: submittedForm,
firstName: "Jimbo",
lastName: "Cruise",
patientEmail: "[email protected]",
})
.end(function(err, res){
...
submittedFormId = res.body._id;
done();
});
});
});
describe('GET /api/form/:form_id', function(){
it('should respond with submitted form data', function(done){
request(url)
.get('/api/form/patient/' + submittedFormId)
.query({email: credentials.email, token: credentials.token})
.end(function(err, res){
res.body.should.have.property('_id');
...
done();
});
});
});
});
after(function() {
exit();
});
});
No matter what timeout I give the Test Suite, it gives "Error: timeout of 5000ms exceeded". All tests pass except for the "it should run the test suite". I would also note that I have other test files that do not use the wrapper. This test suite above is called first, the admin user is created, the test suite timesout, then clears the admin document, then goes on to the other tests. Finally, it prints out the tests that were wrapped around the ConfigureAdmin function.
Upvotes: 1
Views: 735
Reputation: 151411
Inside your wrapper, you have this:
// Call the actual test suite, pass it the auth credentials.
describe("Test Suite", function() {
it("should run the test suite", function(done) {
// No matter what the timeout is set to it still exceeds it
this.timeout(5000);
test_suite({
email: email,
password: password,
token: token,
admin: admin
}, done);
});
});
And the test_suite
function contains more calls to describe
and it
. Mocha won't raise any error if you do this but it will not work the way you'd expect. Mocha executes tests like this:
Mocha discovers the tests. describe
calls register new suites with Mocha. Their callbacks are executed right away. it
calls register new tests with Mocha. Their callbacks are executed when Mocha runs the tests. Hooks calls (before
, after
, etc.) are also registering hooks with Mocha, to be executed later, when Mocha runs the tests.
Mocha runs the tests that were registered.
When you put describe
inside it
there's a problem: this describe
will be executed and Mocha will register a new suite, but by the time it is registered, the flow of execution is outside all of your describe
callbacks. So this new suite is registered on an anonymous top-level suite (that Mocha creates automatically) and inherits its timeout value from that top-level suite. Check out this example:
describe("top", function () {
it("test", function () {
this.timeout(5000);
describe("inner", function () {
it("inner test", function (done) {
setTimeout(function () {
done();
}, 6000);
});
});
});
describe("inner 2", function () {
it("inner test 2", function () {});
});
});
describe("top 2", function (){
it("test 3", function () {});
});
If you run it, you get:
top
✓ test
inner 2
✓ inner test 2
top 2
✓ test 3
inner
1) inner test
3 passing (2s)
1 failing
1) inner inner test:
Error: timeout of 2000ms exceeded
[... etc ...]
Note how the inner
suite, even though it appears inside top
in the JavaScript code is shown outside of it in Mocha's report. (inner 2
, on the other hand, appears exactly where it should.) This is what I was explaining above: by the time Mocha registers this suite, the flow of execution is outside the top
and top 2
describe
calls. Note also how the timeout
call is useless.
If you run the same code above but with mocha --timeout 7000
, the test will pass because the default timeout value, including for the anonymous suite Mocha creates is now 7000.
Also, your suite currently requires a certain order between the tests. Mocha is not designed for this. Setting up the fixtures for your tests should be done in before
or beforeEach
hooks, and tearing them down should be done in after
and afterEach
. So it is not just a matter of taking the describe
out of the it
call.
Upvotes: 1