I'm writing tests for a very early stage Node/Express app. I've made the decision to use only FB logins for most users, so many of my controllers depend on the authenticated state from FB. But I'm not clear how to either test this, or how to preserve the session with the logged in user in a way that passport, which I'm using, will understand.
var expect = require("chai").expect;
var mongoose = require('mongoose');
var User = require('../models/users');
var nock = require('nock');
var parser = require('cookie-parser')
var session = require('supertest-session');
var agent = require('superagent');
var _ = require('lodash');
var app = require('../app');
var testSession = null;
var fbRedirectURL;
beforeEach(function () {
testSession = session(app);
});
it('should authorize with FB', function (done) {
testSession.get('/auth/facebook')
.expect(302)
.end(function(err, res){
fbRedirectURL=decodeURIComponent(res.headers.location);
done()
});
});
it('should get FB data', function (done) {
testSession.get(fbRedirectURL)
.expect(200)
.end(function(err,res) {
console.log()
done()
})
});
These tests work, but the second throws an error: and I am not clear how I use this to maintain a session through other tests, or if I have to somehow mock that, or otherwise try to fool passport.
I've looked high and low, and haven't found much of anything that seems to address the issue of testing with third-party authentication services, and it seems problematic. Are there resources I haven't found? Even passport-facebook's documentation seems to glide over the topic of testing code that depends on FB authenication, and provides a test suite for only their own code.
Thanks in advance.
I couldn't make headway, and I've moved to using E2E testing with nightwatch.js and selenium. Took about an hour to figure it out, and get tests of FB logins: now I just have to see if I can integrate mocha tests with the nightwatch ones…
Related
I'm trying to create an integration test using Jest for my Express app. I think I have a conceptual misunderstanding as my tests are behaving strangely. My goal is to test the following scenario. I'm hitting a specific endpoint using Supertest, and I want to check whether an error handler middleware is called if there is a mocked error. I want to check whether the error handler is not called, if there is no error present. I have the following test file:
test.js
const request = require('supertest')
describe('Error handler', () => {
let server
let router
beforeEach(() => {
jest.resetModules()
jest.resetAllMocks()
})
afterEach(async () => {
await server.close()
})
it('should be triggered if there is a router error', async () => {
jest.mock('../../routes/')
router = require('../../routes/')
router.mockImplementation(() => {
throw new Error()
})
server = require('../../server')
const res = await request(server)
.get('')
.expect(500)
.expect('Content-Type', /json/)
expect(res.body.error).toBe('Error')
expect(res.body.message).toBe('Something went wrong!')
expect(res.body.status).toBe(500 )
})
it('should not be triggered if there is no router error', async () => {
server = require('../../server')
const res = await request(server)
.get('')
.expect(201)
.expect('Content-Type', /text/)
})
})
What I think is happening is the following. Before each test I reset all modules, because I don't want to have the cached version of my server from the first require, I want to overwrite it. I also reset all mocks, so when the second test runs, no mock is used, no fake error is forced, so the middleware is not called and I'm getting back a vanilla 200 result.
After this is done, I start testing the scenario when there is an error. I mock the routes file that exports my routes so I can force a fake error. Then I require the server, this way, I suppose, it's loading the server up with the fake, error throwing route. Then I wait for the response with Supertest, and assert that I indeed got an error back - hence the error handler middleware has been triggered and worked.
The afterEach hook is called, the server is closed, then the beforeEach hook initializes everything, again. Now I have my vanilla implementation without the mock. I require my server, hit the homepage with a get request, and I get back the correct response.
The strange thing is that for some reason the second test seems to not exit gracefully. If I change my implementation from async - await in the second test, to specify the done callback, and then if I call it at the end of the test, it seems to be working.
I tried a lot of possible permutations, including putting the mocking part to the beforeEach hook, starting the server before / after mocking, and I got weird results. I feel like I have conceptual misunderstandings, but I don't know where, because there are so many moving parts.
Any help to make me understand what is wrong would be greatly appreciated
EDIT:
I thought that most parts can be considered a black box, but now I realize that the fact that I'm trying to create an app using Socket.IO makes the setup process a bit more convoluted.
I don't want Express to automatically create a server for me, because I want to use socketIO. So for now I only create a function with the appropiate signature, and that is 'app'. This can be given as an argument to http.Server(). I configure it with options and the middlewares that I want to use. I do not want to call app.listen, because that way Socket.IO could not do its own thing.
config.js
const path = require('path')
const express = require('express')
const indexRouter = require('./routes/')
const errorHandler = require('./middlewares/express/errorHandler')
const app = express()
app.set('views', path.join(__dirname + '/views'))
app.set('view engine', 'ejs')
app.use(express.static('public'))
app.use('', indexRouter)
app.use(errorHandler)
module.exports = app
In server.js I require this app, and then I create a HTTP server using it. After that, I feed it to 'socket.io', so it is connected to the proper instance. In server.js I do not call server.listen, I want to export it to a file that actually starts up the server (index.js) and I want to export it to my tests, so Supertest can spin it up.
server.js
// App is an Express server set up to use specific middlewares
const app = require('./config')
// Create a server instance so it can be used by to SocketIO
const server = require('http').Server(app)
const io = require('socket.io')(server)
const logger = require('./utils/logger')
const Game = require('./service/game')
const game = new Game()
io.on('connection', (socket) => {
logger.info(`There is a new connection! Socket ID: ${socket.id}`)
// If this is the first connection in the game, start the clock
if (!game.clockStarted) {
game.startClock(io)
game.clockStarted = true
}
game.addPlayer(socket)
socket.on('increaseTime', game.increaseTime.bind(game))
})
module.exports = server
If I understand everything correctly, basically the same thing happens, expect for a few additional steps in the example that you provided. There is no need to start the server, and then use Supertest on it, Supertest handles the process of starting up the server when I use request(server).get, etc.
EDIT 2
Right now I'm not sure whether mocking like that is enough. Some mysterious things leaves the Supertest requests hanging, and it might be that somewhere along the way it can not be ended, although I do not see why would that be the case. Anyway, here is the router:
routes/index.js
const express = require('express')
const router = express.Router()
router.get('', (req, res, next) => {
try {
res.status(200).render('../views/')
} catch (error) {
next(error)
}
})
router.get('*', (req, res, next) => {
try {
res.status(404).render('../views/not-found')
} catch (error) {
next(error)
}
})
module.exports = router
The order of requiring and mocking is correct but the order of setting up and shutting down a server probably isn't.
A safe way is to make sure the server is available before doing requests. Since Node http is asynchronous and callback-based, errors cannot be expected to be handled in async functions without promisification. Considering that server.listen(...) was called in server.js, it can be:
...
server = require('../../server')
expect(server.listening).toBe(true);
await new Promise((resolve, reject) => {
server.once('listening', resolve).once('error', reject);
});
const res = await request(server)
...
close is asynchronous and doesn't return a promise so there's nothing to await. Since it's in a dedicated block, a short way is to use done callback:
afterEach(done => {
server.close(done)
})
In case errors are suppressed in error listener, server.on('error', console.error) can make troubleshooting easier.
Supertest can handle server creation itself:
You may pass an http.Server, or a Function to request() - if the server is not already listening for connections then it is bound to an ephemeral port for you so there is no need to keep track of ports.
And can be provided with Express instance instead of Node server, this eliminates the need to handle server instances manually:
await request(app)
...
I'm trying to test an authenticated endpoint in my app. My node app uses express, express session, passport-local, react and next for auth.
I've spent way too many hours trying to solve this problem and could not find a solution.
Basically my test would like to:
send a login request and login
send a request to an authenticated route
receive the appropriate response
My issue was that I had no persistence between the login request and the authenticated route request.
When I sent the login request, passport serializes the user and sets req.user and req._passport.session to the appropriate values.
On the next request - the authenticated route, my middleware looks for req.passport._session or req.user but neither exist. I would get the 401 unauthorized response.
I posted my solution below that took me way too long to figure out.
I solved the persistence issue with Chai HTTP - which uses superagent.
The solution was pretty simple once I had the correct tools. I used the tutorial from the chai-http page but changed it to use async await and try catch.
const { assert } = require('chai');
const chai = require('chai');
const { expect } = require('chai');
const chaiHttp = require('chai-http');
chai.use(chaiHttp);
describe('/authenticatedRequest', () => {
it('should respond appropriately', async () => {
const agent = chai.request.agent('http://localhost:8000');
try {
await agent
.post('/rootPath/loginLocal')
.send({ email: 'email#email.com', password: 'password' });
const authenticatedResponse = await agent.get('/rootPAth/authenticatedRoute');
assert.deepEqual(successResponse, workoutRes);
} catch (e) {
console.log(e);
}
});
});
I now see this post How to authenticate Supertest requests with Passport? - which would have saved me a lot of time.
I hope adding having another post will help someone else with this.
I would like to write unit tests using Mocha for my Nodejs/Express app that I have written in visual studio. I have scoured everywhere I could looking for a simple tutorial but not found what I am looking for. I have seen many tutorials in creating a test using assert to test that 5=5, etc. but that's not what I want to do.
I am trying to add a JavaScript Mocha Unit Test file through VS and then all I really want it to do is open the home page of my app, check for some content in the body and pass the test. If I want to run the tests from the Test Explorer window the nodejs app can't be running and if it isn't running there would be nothing to receive the request for the homepage.
So I'm not sure if the test itself is somehow supposed to launch the app or what? I feel like I'm in a catch 22 and missing the very basics, just don't see it described anywhere.
What you're looking for is most commonly called an API test - a part of integration testing, not a unit test. If a test touches network, a database or I/O it's, most commonly, an integration test instead.
Now to your question. In order to test your app.js code without starting up the server manually beforehand you can do the following:
module.export your app server.
In your tests, use chai-http to test routes.
require your app in the tests and use that instead of URL's when testing routes.
The key here is the 1st bullet point. You must export your app so you can require it and use it in your tests. This allows you to skip the part where you start a separate server process to run the tests on.
Server code
// app.js
const express = require('express')
const app = express()
const bodyParser = require('body-parser')
app.use(bodyParser.json())
// Routes
app.post('/register', (req, res) => {
const requiredFields = ['name', 'email']
if (requiredFields.every(field => Object.keys(req.body).includes(field))) {
// Run business logic and insert user in DB ...
res.sendStatus(204)
} else {
res.sendStatus(400)
}
})
app.listen(3000)
// export your app so you can include it in your tests.
module.exports = app
Test code
// test/registration.spec.js
const chai = require('chai')
const chaiHttp = require('chai-http')
// `require` your exported `app`.
const app = require('../app.js')
chai.should()
chai.use(chaiHttp)
describe('User registration', () => {
it('responds with HTTP 204 if form fields are valid', () => {
return chai.request(app)
.post('/register')
.send({
name: 'John Doe',
email: 'john#doe.com'
})
.then(res => {
res.should.have.status(204)
})
.catch(err => {
throw err
})
})
it('responds with HTTP 400 if some fields are missing', () => {
return chai.request(app)
.post('/register')
.send({
name: 'John Doe'
})
.catch(err => {
err.should.have.status(400)
})
})
})
Then just run your test from the root directory with:
$ mocha test/registration.spec.js
I am new to Test driven development and I want to test my login API but I cant seem to understand fully how to implement tests with Database and what is the proper way to do it?
First, I am also not an expert in this topic but i have been using this method for quite some time. If anyone find that what i'm writing is wrong or somewhat misleading, please correct me. I am very open to critics and opinions.
As the name suggests, TDD method requires you to write the test before the implementation. Basically, you write the test, see it's failing, write the implementation and repeat until the test is passed.
If you are using express, you may want to use the supertest module. They way to use it is similar to superagent. You can install it by running
npm install supertest --save-dev
I am going to show you a very simple example of how to use it with mocha and chai.
So here's an example of express app:
// file: app.js
const express = require('express');
const app = express();
// your middlewares setup goes here
const server = app.listen(8000, () => {
console.log('Server is listening on port 8000');
});
module.exports = app;
And here's the example test case of the login API:
// file: test/api.js
const request = require('supertest');
const app = require('../app');
const expect = require('chai').expect;
describe('Login API', function() {
it('Should success if credential is valid', function(done) {
request(app)
.post('/api/v1/login')
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.send({ username: 'username', password: 'password' })
.expect(200)
.expect('Content-Type', /json/)
.expect(function(response) {
expect(response.body).not.to.be.empty;
expect(response.body).to.be.an('object');
})
.end(done);
});
});
You may run it with this command
node_modules/mocha/bin/mocha test/**/*.js
The example above assumes that you will implement the login API using POST method at /api/v1/login path. It also assumes that you will receive and respond data with json format.
What the example test case does that it tries to send a POST request to /api/v1/login with the following data:
{
username: 'username',
password: 'password'
}
Then, it expects that your API will respond with 200 response code as shown in this line:
.expect(200)
If it receive a response with code other than 200, the test will fail.
Then, it expect that the Content-Type of your response to be application/json. If the expectation does not meet the reality, the test will also fail.
This code below:
.expect(function(response) {
expect(response.body).not.to.be.empty;
expect(response.body).to.be.an('object');
})
It checks the response from your server. You can use the chai's expect inside the function body as shown above. You may notice that supertest also provide expect method. But, the way to use both supertest's expect and chai's expect is different.
And finally, call end function with done callback so that the test case can be run properly.
You may want to check supertest documentation to get more details on how to use it.
Establishing database connection before testing
If you need to maintain a database connection before running all the test case, here's the idea:
Create another file inside the test directory. For example, database_helper.js. Then, write the following code:
before(function(done) {
// write database connection code here
// call done when the connection is established
});
I've tried it with mongoose before, and it worked for me.
I hope that helps.
I'm using koa-passport & koa to handle my login and registration using twitter oauth. It works great, but I'm having difficulties understanding how I should test my authenticated routes using supertest and mocha.
Most examples I have seen involve using supertest to send a username and password to a login endpoint to first create a user session. My problem is that I don't have a username/passport auth strategy. I need to emulate an oauth login process that will set the appropriate session variables, and therefore test certain routes.
Any clue on how I can achieve this?
My solution these days to this problem is to basically insert a mock authenticated user in the middleware stack temporarily when needed. Here's my basic code for express.
var Layer = require('express/lib/router/layer');
var app = require('../../../server');
exports.login = login;
exports.logout = logout;
function login(user){
var fn = function insertUser(req, res, next){
req.user = user;
next();
}
var layer = new Layer('/', {
sesitive: false,
strict: false,
end: false
}, fn);
layer.route = undefined;
app._router.stack.unshift(layer);
}
function logout(){
app._router.stack.shift();
}
And then within your tests, you call:
it('should allow the user to edit something', function(done){
login(this.user);
// perform supertest task
logout();
});
This is obviously pretty rudimentary... but seems to do the job for testing purposes.