Test redirection using jest in express - node.js

i am using Jest to test my code.
What i want achieve is to test redirection from http to https. (if it exists if process.env.IS_PRODUCTION).
I don't know how to test it, how to mockup this and so on...
I've tried standard get reqest but don't know how to mockup environment varible or test it in different way
it('should redirect from http to https, (done) => {
request(server)
.get('/')
.expect(301)
.end((err, res) => {
if (err) return done(err);
expect(res.text).toBe('...')
return done();
});
}, 5000);
I expect to be able to test this redirection :)

You could use the node-mocks-http libary which allows you to simulate a request and response object.
Example:
const request = httpMocks.createRequest({
method: 'POST',
url: '/',
});
const response = httpMocks.createResponse();
middlewareThatHandlesRedirect(request, response);
I never worked with jest but I believe that you can check the response.location parameter once the middleware has been called

Preface: I'm not familiar with jest or express or node. But I have found it to be much easier to test explicit configuration (instantiating objects with explicit values) vs implicit configuration (environmental variables and implementation switches on them):
I'm not sure what request or server are but explicit approach might look like:
it('should redirect from http to https, (done) => {
const server = new Server({
redirect_http_to_https: true,
});
request(server)
.get('/')
.expect(301)
.end((err, res) => {
if (err) return done(err);
expect(res.text).toBe('...')
return done();
});
}, 5000);
This allows the test to explicitly configure server to the state it needs instead of mucking with the environment.
This approach also helps to keep process configuration at the top level of your application:
const server = new Server({
redirect_http_to_https: process.env.IS_PRODUCTION,
});

Related

Cypress net::ERR_EMPTY_RESPONSE on real server calls

I'm trying to test my fullstack angular-nestjs-application with cypress e2e tests.
Server calls from within angular to not reach my backend running on localhost:443 (I tested it with 0.0.0.0, 127.0.0.1 like some other answers requested - without success.
I also did try to add a local proxy on my machine like some other posts suggested - again without any success).
On the other hand: Requests sent by cy.request('http://localhost:443/...' do actually reach my backend. I am able to send the request in beforeEach, save the response, intercept the real request and feed the saved response data to it.
cy.login() does a login call to build a valid session for my backend.
describe('test', () => {
let data: any;
beforeEach(() => {
cy.login();
cy.request('http://localhost:443/load').then(response => {
data = response;
console.log('BeforeEach Response: ', data);
});
});
it('load data', () => {
cy.visit('/');
});
});
But the following line in beforeEach does work:
cy.request('http://localhost:443/load').then(response => {
data = response;
console.log('BeforeEach Response: ', data);
});
So the following test does work completely:
describe('test', () => {
let data: any;
beforeEach(() => {
cy.login();
cy.request('http://localhost:443/load').then(response => {
data = response;
console.log('BeforeEach Response: ', data);
});
});
it('load data', () => {
cy.intercept('/load', data);
cy.visit('/');
});
});
So what am i missing to successfully test my application with real server requests - without sending the same request by hand and stubing the real one?
I assume your baseUrl in cypress.json is not localhost:443. If that's the case, then for chrome-based browsers you can set chromeWebSecurity to false. See https://docs.cypress.io/guides/guides/web-security#Set-chromeWebSecurity-to-false.
If that doesn't help or you have to test with firefox then you have to put your app and all required backend-services behind a proxy, so that it looks like every request is served by the proxy.
If you have Angular in dev-mode then you already have a proxy and you can configure your backend services via proxy.conf.json. See https://angular.io/guide/build#proxying-to-a-backend-server

How to mock req.session on Mocha/Chai API Unit Test

Using Mocha/Chai for REST API unit testing, I need to be able to mock req.session.someKey for a few of the end points. How can I go about mocking req.session?
I'm working on writing REST API unit tests for a NodeJS Express app that utilizes express-session. Some of these endpoints require the use of data stored in req.session.someKey, the endpoint is setup to return a 400 if req.session.someKey is undefined so I need to be able to mock it in order for the test to complete successfully.
Example code:
router.get('/api/fileSystems', utilities.apiAuth, (req, res) => {
let customer = req.session.customer;
let route = (customer === 'NONE') ? undefined : customer;
if(route == undefined){
res.status(400).send('Can't have customer of undefined');
} else {
let requestOptions = setRequestOptions(route);
queryFileSystemInfo(requestOptions, (info) => {
res.status(200).send(info);
});
}
});
What I've tried:
describe('/GET /api/fileSystems', () => {
it('It should return information about the filesystem for a customer'), (done) => {
chai.request(server)
.get('api/fileSystems')
.set('customer', '146')
.end((err, res) => {
res.should.have.status(200);
done();
});
});
});
I attempted to use the .set() in order to set req.session but I believe that .set just sets the headers so I don't believe that I can update it that way unless I'm missing something.
In your express setup you usually plug in the session middleware like this
app.use(session(config))
instead you can put the session middleware in a handy accessible location, and make a wrapper for it, like this:
app.set('sessionMiddleware') = session(config)
app.use((...args) => app.get('sessionMiddleware')(...args)
Tests will need access to the express instance, you can do this by refactoring /app.js to export a function.
function app () {
const app = express()
// ... set up express
return app
}
// run app if module called from cli like `node app.js`
if (require.main === module) instance = app()
module.exports = app
Then in your test, you can overwrite app.sessionMiddleware
describe('/GET /api/fileSystems', () => {
it('It should return information about the filesystem for a customer'), (done) => {
app.set('sessionMiddleware') = (req, res, next) => {
req.session = mockSession // whatever you want here
next()
}
chai.request(server)
.get('api/fileSystems')
.set('customer', '146')
.end((err, res) => {
res.should.have.status(200);
done();
});
// then you can easily run assertions against your mock
chai.assert.equal(mockSession.value, value)
});
});
The other options I've seen on the net involve setting a cookie to match a session which is stored in the db, the problem with that approach is that you end up running into problems when the session in the db expires, so tests fail over time as fixtures become stale. With the approach outlined above you can work around that by setting expiries in the test.
mock-session is pretty use full to mock your session object
let mockSession = require('mock-session');
describe('/GET /api/fileSystems', () => {
it('It should return information about the filesystem for a customer'), (done) => {
let cookie = mockSession('my-session', 'my-secret', {"count":1}); // my-secret is you session secret key.
chai.request(server)
.get('api/fileSystems')
.set('cookie',[cookie])
.end((err, res) => {
res.should.have.status(200);
done();
});
});
});
For this project, I ended up having to set req.session.customer in our server.js file that has an app.use() call that uses a middleware function to set the current session. I was unable to actually find a package that directly mutates the req.session object at test time.

Using await / async with mocha, chai

I'm quite new to node and express.
And have been trying to write test code using mocha, chai and chai-http.
Here's the part of source code.
const mongoose = require('mongoose'),
User = require('../../models/user');
const mongoUrl = 'mongodb://xxxxxxxxxxx';
describe('/test', function() {
before('connect', function() {
return mongoose.createConnection(mongoUrl);
});
beforeEach(async function(done) {
try {
await User.remove({}); // <-- This doesn't work
chai.request('http://localhost:3000')
.post('/api/test')
.send(something)
.end((err, res) => {
if (err) return done(err);
done();
});
} catch (error) {
done(error);
}
});
});
And I get the following error with "npm test"(nyc mocha --timeout 10000 test/**/*.js).
Error: Timeout of 10000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I confirmed the database connection works properly from log.
And seems I get the timeout error with await User.remove({}).
I've also tried different methods such as a User.save()
But, I got the same error.
Do I need to do something special with database model and connection?
This is all pretty simple.
To avoid the error you must not use both done and async/await in Mocha at the same time. Either use async/await and remove both done as function parameter and done() call. Or use done. Then remove both async/await. See the example tests below for each case.
Use try/catch with async/await as you would normally use it with synchronous code.
Following are the most basic Mocha tests with both async/await and done approaches testing the same basic HTTP server endpoint.
This is async/await approach.
it('with async/await', async function() {
const res = await chai.request(server)
.get('/')
.send();
assert.equal(res.status, 200);
});
This is done approach.
it('with done & callbacks', (done) => {
chai.request(server)
.get('/')
.end((err, res) => {
assert.equal(res.status, 200);
done();
});
});
See the full test file snippet.
For working example additionally spin basic Express server as the tests counterpart in src/app.js.
See Chai HTTP plugin docs for more info on what you can do with request testing.
This is it.
I had the same problem and have not found a way to get any promises that involve mongoose working with Mocha/Chai.
What may help you is doing what I did and putting your mongoose code in a script so you can run it with node <scriptfile>.js. You can use that to confirm it's working properly by itself. In my test, the mongoose operation finished in less than a second. You can also call that file from another (non-test related) to confirm it executes properly and returns a promise. You can see from my example how to make sure you close db properly. Partial example:
...
db.close();
return new Promise((resolve) => {
db.on('disconnected', () => {
console.log('***************************************Mongoose CONNECTION TERMINATED');
resolve('user ready');
});
});
...
You may also find some clues by looking at the following issues here and here.
The work around that I did after wasting too much time trying to figure out this crazy behavior was to perform my mongoose needs in a route. I wrap each request that needs to use it in the end block of the extra chai.request... or use async. Example:
describe('something', () => {
it('should do something and change it back', async () => {
try {
// change user password
let re1 = await chai.request(app)
.post('/users/edit')
.set('authorization', `Bearer ${token}`)
.send({
username: 'user#domain.com',
password: 'password6',
});
expect(re1.statusCode).to.equal(200);
// change password back since before hook not working
let re2 = await chai.request(app)
.post('/users/edit')
.set('authorization', `Bearer ${token}`)
.send({
username: 'user#domain.com',
password: 'password6',
passwordNew: 'password',
passwordConfirm: 'password',
});
expect(re2.statusCode).to.equal(200);
} catch (error) {
// error stuff here
}
});
Note that using the try/catch syntax above will cause test that should normally fail to show passing and the results will be caught in the catch block. If you want to avoid that, just remove the try/catch.
How did you implement ./models/user? await only works if User.remove() returns a promise, not if it expects a callback. I would add debug information to your User.remove() function to see where it gets stuck.

Unable to send authenticated request in tests using Jest, Supertest, Passport, Koa2

Despite my best attempts to correctly write test code to authenticate a request agent in Setup blocks or previous describe/it blocks, any request I make from the agent in subsequent describe/it blocks never completes as 200.
Example code:
const request = require('supertest');
const server = require('../server');
let agent = request.agent(server);
let fakePerson = null;
beforeEach(async (done) => {
fakePerson = await Person.createMock();
agent.post(‘/login’)
.send({
email: ‘test#user.com’,
password: ‘password’
})
.end(function(err, res) {
if (err) throw err;
done();
});
});
describe('GET /users/:id', () => {
it ('renders user profile', () => {
return agent
.get(`/users/${fakePerson.id}`)
.expect(200)
});
});
I thought it might have something to do with how I was forming the async calls syntactically. But after trying different ways of returning the login call in the beforeEach block using return, .end() syntax, even async/await, I've determined (ie given up) that the code must be composed properly. Could it be something else?
Referenced articles/resources:
How to authenticate Supertest requests with Passport?
https://medium.com/#juha.a.hytonen/testing-authenticated-requests-with-supertest-325ccf47c2bb
https://gist.github.com/joaoneto/5152248
https://medium.com/#bill_broughton/testing-with-authenticated-routes-in-express-6fa9c4c335ca
https://github.com/visionmedia/supertest/issues/46
Package versions:
"koa": "^2.4.1"
"koa-passport": "^4.0.1"
"passport-json": "^1.2.0"
"passport-local": "^1.0.0"
"supertest": "^3.0.0"
"jest": "^22.1.3"
I took some time stepping through my auth code during test runs and couldn't see any obvious problem. Then I had a thought: what if the request itself was ill formed. Turns out I was right! Inspecting the set-cookie header in the Supertest response headers I saw:
[ 'koa:sess=eyJwYXNzcG9ydCI6eyJ1c2VyIjoxfSwiX2V4cGlyZSI6MTUyMTIyODg0NTU5OSwiX21heEFnZSI6ODY0MDAwMDB9; path=/; httponly,koa:sess.sig=EEZzgcg3bx8bm_FXRMobrr8_Yts; path=/; httponly' ]
This looked a little suspicious as a single string, which led me to some more googling where I discovered that there were differences in the way that cookie headers were being set for Mocha and Jest users as global state on the Supertest agent instance. See: https://github.com/facebook/jest/issues/3547#issuecomment-302541653. Folks using Mocha had no trouble authenticating, while Jest users did. Turns out there is a bug with Jest globals that is causing the cookie to come in as a single string rather than an array of separate strings for each cookie--which is what Supertest needs to format the request properly.
Here is a workaround based on the code in the question where we correctly parse the buggy Jest string to a scoped variable for the cookie/session data inside the Setup block:
const request = require('supertest');
const server = require('../server');
let agent = request.agent(server);
let fakePerson = null;
let session = null;
beforeEach(async () => {
fakePerson = await Person.createMock();
agent.post(‘/login’)
.send({
email: fakePerson.email,
password: fakePerson.password’
})
.then(res => {
session = res
.headers['set-cookie'][0]
.split(',')
.map(item => item.split(';')[0])
.join(';')
expect(res.status).toEqual(200)
});
});
describe('GET /users/:id', () => {
it ('renders user profile', () => {
return agent
.get(`/users/${fakePerson.id}`)
.set('Cookie', session)
.expect(200)
});
});
I couldn't get internetross's answer to work. Finally after lots of sleuthing, I found this: https://github.com/facebook/jest/issues/3547#issuecomment-397183207.
I had to replace
session = response.headers['set-cookie'][0]
.split(',')
.map(item => item.split(';')[0])
.join('; ')
with
response.headers['set-cookie'][0]
.split(',')
.map(item => item.split(';')[0])
.forEach(c => agent.jar.setCookie(c));
Big sigh.

Loopback testing upload file as a user with role

I’ve been writing tests for my loopback backend using loopback-testing project.
The backend has set loopback-component-storage in order to provide apis to store files in the filesystem. I want to test file upload using the remote api that loopback-component-storage provides using something like this:
describe('Containers', function() {
lt.it.shouldBeAllowedWhenCalledByUserWithRole(TEST_USER, someRole,
'POST', '/api/containers/somecontainer/upload', somefile);
});
But with no luck... There's no documentation about this. I don't know if it is even possible to test. Any ideas?
Thanks in advance
Some links:
https://github.com/strongloop/loopback-testing
https://github.com/strongloop/loopback-component-storage
loopback-testing is currently deprecated.
You should consider using supertest instead. It relies on superagent, and allows you to perform http requests on your REST api and assert on response objects.
Then, you can use the attach method of super-agent to build a multipart-form-data request that can contain a file.
Code using mocha for describing test looks then like this:
var request = require('supertest');
var fs = require('fs');
var app = require('./setup-test-server-for-test.js');
function json(verb, url) {
return request(app)[verb](url)
.set('Content-Type', 'multipart/form-data');
};
describe("User",function() {
it("should be able to add an asset to the new project", function(done){
var req = json('post', '/api/containers/someContainer/upload?access_token=' + accessToken)
.attach("testfile","path/to/your/file.jpg")
.expect(200)
.end(function(err, res){
if (err) return done(err);
done();
});
});
it("should have uploaded the new asset to the project folder", function(done){
fs.access('/path/to/your/file.jpg', fs.F_OK, function(err){
if (err) return done(err);
done();
});
});
};

Resources