I have some Sails.js API tests (using Mocha) that make use of SuperTest's .end() method to run some Chai assertions on the response.
I call the test's done() callback after the assertions, but if an assertion error is thrown, the test times out.
I can wrap the assertions in a try/finally, but this seems a bit icky:
var expect = require('chai').expect;
var request = require('supertest');
// ...
describe('should list all tasks that the user is authorized to view', function () {
it('the admin user should be able to see all tasks', function (done) {
var agent = request.agent(sails.hooks.http.app);
agent
.post('/login')
.send(userFixtures.admin())
.end(function (err, res) {
agent
.get('/api/tasks')
.expect(200)
.end(function (err, res) {
try {
var tasks = res.body;
expect(err).to.not.exist;
expect(tasks).to.be.an('array');
expect(tasks).to.have.length.of(2);
} finally {
done(err);
}
});
});
});
});
Any suggestions on how to better deal with this? Perhaps Chai HTTP might be better?
According to Supertest's documentation, you need to check for the presence of err and, if it exists, pass it to the done function. Like so
.end(function (err, res) {
if (err) return done(err);
// Any other response-related assertions here
...
// Finish the test
done();
});
You can pass out login logic from test.
// auth.js
var request = require('supertest'),
agent = request.agent;
exports.login = function(done) {
var loggedInAgent = agent(sails.hooks.http.app);
loggedInAgent
.post('/login')
.send(userFixtures.admin())
.end(function (err, res) {
loggedInAgent.saveCookies(res);
done(loggedInAgent);
});
};
And then just use it in your test:
describe('should list all tasks that the user is authorized to view', function () {
var admin;
before(function(done) {
// do not forget to require this auth module
auth.login(function(loggedInAgent) {
admin = loggedInAgent;
done();
});
});
it('the admin user should be able to see all tasks', function (done) {
admin
.get('/api/tasks')
.expect(function(res)
var tasks = res.body;
expect(tasks).to.be.an('array');
expect(tasks).to.have.length.of(2);
})
.end(done);
});
it('should have HTTP status 200', function() {
admin
.get('/api/tasks')
.expect(200, done);
});
});
With such approach you should not login your admin for each test (you can reuse your admin again and again in your describe block), and your test cases become more readable.
You should not get timeouts with this approach because .end(done) guaranties that your test will finish without errors as well with them.
Related
I am testing CRUD API-Operations (https://api.sap.com/api/OP_API_MAINTNOTIFICATION/overview?ReleaseInfo=2021%20FPS02), with mochajs and chai.
let chai = require("chai");
let chaiHttp = require("chai-http");
//Assertion Style
chai.should();
chai.use(chaiHttp);
describe("API TEST", function () {
it.only('Get all notifications', function (done) {
const url = "https://sandbox.api.sap.com/s4hanacloud/sap/opu/odata/sap/API_MAINTNOTIFICATION/MaintenanceNotification";
/**/
chai.request(url)
.post('/auth/sign_in')
// send user login details
.send({
'user': 'ExampleEmail',
'password': 'ExamplePassword'
})
.end(function (err, res) {
var token = res.body.token;
console.log(token);
done();
});
chai.request(url)
.get('')
.set("Authorization", "Bearer " + token)
.end(function (err, res) {
done();
});
});
});
Issue
In order to test the GET function it is required for the user to authorize oneself
{"fault":{"faultstring":"Failed to resolve API Key variable request.header.apikey","detail":{"errorcode":"steps.oauth.v2.FailedToResolveAPIKey"}}}
I did the authorization in this manner / gotten the token but it doesn't work yet(the token is empty), as well as the path "/auth/sign_in" seemingly isn't working
Questions
How to do the Authorization for an API in mochajs in order to test said API ?
The answer to the question has been found.
Send the following authorization with any CRUD Operation:
.auth('Username', 'Password')
Credit: Setting Basic Auth in Mocha and SuperTest #yves-m
I am using Jest to test my Node REST API.
The problem I have currently is that when I try testing my POST routes, I always receive a status code of 302 due to res.redirect("/").
Example of my POST route:
app.post("/login", async (req, res) => {
try {
let username = 'example'
...
return res.redirect("/");
} catch (error) {
return res.redirect("/");
}
});
jest test file:
'use strict';
const request = require('supertest');
const app = require('./index');
...
describe('Test', () => {
test('POST /login', () => {
return request(app)
.post('/login')
.set('username','example')
.expect(?)
});
});
How can I test that the page has redirected successfully?
As per the Express docs, you can specify a response code as such:
res.redirect(301, 'http://example.com')
The docs state "If not specified, status defaults to “302 “Found”."
Edit: HTTP codes 301 and 302 indicate successful redirection; 301 is permanent and 302 is temporary. Both are "successful" as far as a computer is concerned.
I assert the response.headers.location for redirection location. This way I can write test cases by mocking a single class function that causes different redirections.
test('Should handle "/redirectUri"', async () => {
const exchangeForAuthTokenSpy = jest.spyOn(
OAuth.prototype,
'exchangeForAuthToken',
)
exchangeForAuthTokenSpy.mockResolvedValue({
success: true,
access_token: 'access_token',
})
const app = server('', AuthRoutes)
const res = await request(app).get('/redirectUri?code=code&state=state')
expect(exchangeForAuthTokenSpy).toHaveBeenCalledTimes(1)
expect(exchangeForAuthTokenSpy).toHaveBeenCalledWith('code', 'state')
expect(res.status).toEqual(301)
expect(res.headers.location).toContain(
'/callback?code=200&token=access_token',
)
})
It is late, but could help someone. You can test like below
it('redirection test', function (redirect) {
request(app)
.get('/url')
.expect(302, redirect)
});
I'm writing Jasmine tests for a Node.js API. I'm trying to test the create a user functionality. The test would look like this:
describe('User test', function() {
describe('Post /api/saveUser'), function() {
it('saves a new user', function(done) {
request.post({url: 'http://website/api/saveUser', form:{email:testU.email, password:testU.password}, headers: {'authorization': adminU.jwt}}, function(err, res, body) {
expect(res.statusCode).toBe(200);
});
});
});
});
So I need to authenticate the admin user (adminU) in the spec to get the valid token to pass in the request. This is done using another endpoint.
request.post({url: 'http://website/api/authenticate', form:{email:adminU.email, password: adminU.password}}, function(err, res, body) {
adminUser.jwt = JSON.parse(res.body).token;
});
But how do I combine these. If I slot the authentication code above the User test block, the user tests are run before the response from the authentication endpoint is received. The obvious choice is to wrap the user tests in the callback from the authenticate api, but then when I run the tests Jasmine doesn't actually run them.
What's the best way to use the results of a callback in the Jasmine specs?
You need to wait until the request of the auth call is finished.
it('saves a new user', function(done) {
request.post({
url: 'http://website/api/authenticate',
form: {
email: adminU.email,
password: adminU.password
}
}, function(err, res, body) {
adminUser.jwt = JSON.parse(res.body).token;
request.post({
url: 'http://website/api/saveUser',
form: {
email: testU.email,
password: testU.password
},
headers: {
'authorization': adminU.jwt
}
}, function(err, res, body) {
expect(res.statusCode).toBe(200);
done();
});
});
});
If you need the token in more then one test, i would recommend to put it in the before block. If you do this, jasmine runs this once before starting the test and you don't have to make the auth call in every test case.
before(function(done){
request.post({
url: 'http://website/api/authenticate',
form: {
email: adminU.email,
password: adminU.password
}
}, function(err, res, body) {
if(err) done(err);
adminUser.jwt = JSON.parse(res.body).token;
done();
});
});
Also, i recommend supertest for testing apis. See https://www.npmjs.com/package/supertest
I'm going off of this tutorial, trying to make tests with Mocha, Supertest, and Should.js.
I have the following basic test to create a user through a PUT endpoint that accepts it's data in headers.
describe('User Routes', function () {
it('should allow me to make my user', function (done) {
request(url)
.put('/users')
.set(myCreds)
// end handles the response
.end(function(err, res) {
if (err) {
throw err;
}
// this is should.js syntax, very clear
res.should.have.status(201);
done();
});
});
However, while the endpoint does trigger, and the user does get made, the code throws an error that should is undefined... Uncaught TypeError: undefined is not a function.
I have
var should = require('should');
var assert = require('assert');
var request = require('supertest');
At the top of the file, so why would it be undefined?
You are calling should incorrectly, try this:
res.should.have.property('status', 201)
or
res.status.should.be.equal(201)
or
should.equal(res.status, 201)
or install should-http.
I have the following Controller for my login page:
// Authentication Controller
// the basics of Passport.js to work.
var AuthController = {
// localhost:1337/login Render the login page
// <form role="form" action="/auth/local" method="post">
// <input type="text" name="identifier" placeholder="Username or Email">
// <input type="password" name="password" placeholder="Password">
// <button type="submit">Sign in</button>
// </form>
login: function(req, res) {
var strategies = sails.config.passport,
providers = {};
// Get a list of available providers for use in templates.
Object.keys(strategies).forEach(function(key) {
if (key === 'local') return;
providers[key] = {
name: strategies[key].name,
slug: key
};
});
// Render the `auth/login.ext` view
res.view({
providers: providers,
errors: req.flash('error')
});
},
// Log out a user and return them to the homepage
// Passport exposes a logout() function on req (also aliased as logOut()) that
// can be called from any route handler which needs to terminate a login
// session. Invoking logout() will remove the req.user property and clear the
// login session (if any).
logout: function(req, res) {
req.logout();
res.redirect('/login');
},
// The registration form is Just like the login form
register: function(req, res) {
res.view({
errors: req.flash('error')
});
},
// Create a third-party authentication endpoint
provider: function(req, res) {
passport.endpoint(req, res);
},
// Create a authentication callback endpoint
// This endpoint handles everything related to creating and verifying Pass-
// ports and users, both locally and from third-aprty providers.
// Passport exposes a login() function on req (also aliased as logIn()) that
// can be used to establish a login session. When the login operation
// completes, user will be assigned to req.user.
callback: function(req, res) {
passport.callback(req, res, function(err, user) {
req.login(user, function(err) {
// If an error was thrown, redirect the user to the login which should
// take care of rendering the error messages.
if (err) {
res.redirect('/login');
}
// Upon successful login, send the user to the homepage were req.user
// will available.
else {
res.redirect('/');
}
});
});
}
};
module.exports = AuthController;
I am using Mocha as my test framework. The application is based on Sails.
How would I write Mocha test cases and run them on the provided Controller?
I'm using supertest to call controllers as a user, to do so, first lift the sails in the before function so it can be used as the server by supertest.
before(function (done) {
Sails.lift({
// configuration for testing purposes
log: {
//level: 'silly'
level: 'silent'
},
hooks: {
grunt: false,
},
}, function (err, sails) {
done(err, sails);
});
}
Then initialize supertest with the url of your sails app
request = require('supertest');
agent = request.agent(appurl);
You can now write test to post / get data to test your controller from frontend as would a client.
it('should do the post and return whatever', function (done) {
agent.post('/controller/function')
.send(the_post_object_with_params)
.end(function (err, res) {
if (err) {
console.log(err);
}
console.log(res.body); // the content
done();
});
}
I think the main thing is to include sails application into your testcase. I also found no examples with controllers tests but some with models:
Is it possible to use the Mocha Framework against a model in Sails.js?
Cannot unit test my model in sailsjs