How to test Node redirection with Jest - javascript

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)
});

Related

How do I query with url parameter?

I am new to Node.js and trying to check if an e-mail is already taken by sending the email as a url parameter from iOS app. It is not working, not sure what I am doing wrong.
I am unable to console.log the email parameter in VSCode sent from the front-end, it DOES print in XCODE ( http://localhost:3000/api/user/email/test#gmail.com ) and I know the backend is getting the GET request.
My router code is:
const express = require(`express`)
const router = new express.Router()
const User = require(`../models/user-model`) // import User model
router.get(`/api/user/email/:email`, async (req, res) => {
console.log(req.params) // does NOT print email: test#gmail.com
try {
const user = await User.findOne(req.params.email)
if (user) {
console.log(user._id)
res.send({ available: false })
} else {
res.send({available: true})
}
} catch {
res.status(404).send()
}
})
Thank you!
const express = require(`express`)
const app = new express();
app.get(`/api/user/email/:email`, async (req, res) => {
console.log(req.params) // does NOT print email: test#gmail.com
try {
// const user = await User.findOne(req.params.email)
const user = {_id:123};
if (user) {
console.log(user._id)
res.send({ available: false })
} else {
res.send({available: true})
}
} catch {
res.status(404).send()
}
})
app.listen(3000,function(){
console.log("running");
})
Editing this.. I dont have enough points to comment.. your route seems to be fine, maybe you are not telling your application to use this route, somewhere before starting your application you should have something like:
this.app = new express();
...
this.app.use('/api', MailRouter); //<=== Adding your required mail route
...
I use to split url one parte here (/api) and the other one in the router (/user/email/:email). I'm not sure how to do it by adding it fully to the router (Maybe '/' maybe '')

Test request with FormData in jest (without mocking)

I want to test a function with jest that makes a http post request to a server by using the javascript FormData. here is a minimal example of what this could look like:
import axios from "axios";
async function foo() {
let bodyFormData = new FormData();
bodyFormData.append("foo", "myfoo");
resp = await axios({
method: "post",
url: "https://postman-echo.com/post",
data: bodyFormData,
});
return resp;
}
describe("Test", () => {
it("foo test", async () => {
return foo().then(function (result) {
console.log(result);
// here some testing stuff ...
});
});
});
from my research jest cannot use the FormData(). How can i workaround that? Ive seen some solutions here on SO but all of them are somehow mocking the FormData but the probmle with that is then never a real request is send but i want to test if the real request to the real server in working. How can I do that with jest? I've also tried https://github.com/form-data/form-data but it is not working too
Actually you need to use supertest module like that
const supertest = require('supertest');
const app = require('../app');
const request = supertest(app)
describe('POST "/" root endpoint for login & authentication', function() {
it('/register method with missing parameter returns', function(done) {
request
.post('/register')
.send('username=john&password=12345') // x-www-form-urlencoded upload
.set('Accept', 'application/json')
.expect(200)
.end((err, res) => {
if(err) return done(err);
expect(res.body.message).toBe("Username or password is not specified");
return done();
});
});
});
Also you can check this post

Auth0 login before Mocha/Chai test - NodeJS

I'm attempting to test a NodeJS app using Mocha and Chai. I'm using Auth0 to handle the login and signups for the app. I want to be able to test that, once logged in, the user can visit a set of pages, however I'm having trouble handling the actual logging in.
The .get('/login') redirects me to the Auth0 login page, as I expect. However, despite the .send(userCredentials) providing valid login details, it doesn't seem to login. After login, I expect it to redirect to 'localhost:3000/user' but as far as I can tell the final redirect is to the Auth0 login URL, and I'm unsure if this redirect is what prevents the send, or if the fact that the redirect takes a second or two could be causing the issue.
My test file is below.
var chai = require("chai");
var chaiHTTP = require("chai-http");
var chaiAP = require("chai-as-promised");
var server = require("../app");
var listing = require('../models/RDSModel');
var should = chai.should();
var expect = chai.expect;
var request = require("supertest");
const {Client} = require('pg');
var config = require('../config');
const connection = {
user: 'Admin',
host: process.env.DB_URL,
database: 'postgres_test',
password: config.postgresPass,
port: 5432,
};
var pg = require('knex')({
client: 'pg',
connection: connection,
searchPath: ["knex", "public"]
});
chai.use(chaiHTTP);
chai.use(chaiAP);
describe("Listing.js", function() {
beforeEach("login before each test", function(done) {
const userCredentials = {
email: 'hello#email.co.uk',
password: 'this_is_a_password!'
};
request(server)
.get('/login')
.send(userCredentials)
.end(function(err, res) {
console.log(res);
expect(res).to.have.status(302);
expect(res.body.state).to.be.true;
res.body.data.should.be.an("object");
done();
})
});
describe("get /", function() {
it("#return 200 status code", function(done) {
this.timeout(5000);
chai.request(server)
.get('/Listing/')
.end(function(err, res) {
console.log(res.body);
expect(res).to.have.status(200);
expect('Location', '/Listing')
expect(res.body).to.be.an("object");
done();
})
})
});
describe("get /Ignore", function() {
it("should return 200 status code", function(done) {
this.timeout(5000);
chai.request(server)
.get('/Listing/Ignore')
.end(function(err, res) {
expect(res).to.have.status(200);
expect(res.body).to.be.an("object");
done();
})
})
})
describe("get /Report", function() {
it("should return 200 status code", function(done) {
this.timeout(5000);
chai.request(server)
.get('/Listing/Report')
.end(function(err, res) {
expect(res).to.have.status(200);
expect(res.body).to.be.an("object");
done();
})
})
})
})
The reason it gives for failure is:
1) Listing.js
"before each" hook: login before each test:
Uncaught AssertionError: expected undefined to be true
at Test.<anonymous> (test/test-listing.js:44:41)
at Test.assert (node_modules/supertest/lib/test.js:181:6)
at localAssert (node_modules/supertest/lib/test.js:131:12)
at /Users/<Name>/Documents/Node/NodeStuff/node_modules/supertest/lib/test.js:128:5
at Test.Request.callback (node_modules/supertest/node_modules/superagent/lib/node/index.js:728:3)
at IncomingMessage.<anonymous> (node_modules/supertest/node_modules/superagent/lib/node/index.js:916:18)
at IncomingMessage.EventEmitter.emit (domain.js:476:20)
at endReadableNT (_stream_readable.js:1183:12)
at processTicksAndRejections (internal/process/task_queues.js:80:21)
The body of res is empty, as is the text, which seems odd to me.
Any help would be appreciated.
You are trying to integrate your test suite to an interactive login route. What you actually need is non-interactive login. With Auth0, this can be achieved by POST oauth/token endpoint You will need user's email as well as the verification code sent to that email address ( so that you cannot use unverified emails ) and client secret:
https://auth0.com/docs/api/authentication#authenticate-user
Or, you can use the Management Token obtained by Management API to execute actions.

How to test image upload (stream) with supertest and jest?

I have an image upload endpoint in my API that accepts application/octet-stream requests and handles these streams. I'd like to write test coverage for this endpoint but cannot figure out how to use supertest to stream an image.
Here's my code so far:
import request from 'supertest'
const testImage = `${__dirname}/../../../assets/test_image.jpg`
describe('Upload endpoint', () => {
test('Successfully uploads jpg image', async () =>
request(app)
.post(`${ROOT_URL}${endpoints.add_image.route}`)
.set('Authorization', `Bearer ${process.env.testUserJWT}`)
.set('content-type', 'application/octet-stream')
.pipe(fs.createReadStream(testImage))
.on('finish', (something) => {
console.log(something)
}))
})
This code produces nothing, the finish event is never called, nothing is console logged, and this test suite actually passes as nothing is expected. I cannot chain a .expect onto this request, otherwise I get this runtime error:
TypeError: (0 , _supertest2.default)(...).post(...).set(...).set(...).pipe(...).expect is not a function
How is such a thing accomplished?
This should work. To pipe data to a request you have to tell the readable stream to pipe to the request. The other way is for receiving data from the server. This also uses done instead of async as pipes do not work with async/await.
Also worth nothing is that by default the pipe will call end and then superagent will call end, resulting in an error about end being called twice. To solve this you have to tell the pipe call not to do that and then call end in the on end event of the stream.
import request from 'supertest'
const testImage = `${__dirname}/../../../assets/test_image.jpg`
describe('Upload endpoint', () => {
test('Successfully uploads jpg image', (done) => {
const req = request(app)
.post(`${ROOT_URL}${endpoints.add_image.route}`)
.set('Authorization', `Bearer ${process.env.testUserJWT}`)
.set('content-type', 'application/octet-stream')
const imgStream = fs.createReadStream(testImage);
imgStream.on('end', () => req.end(done));
imgStream.pipe(req, {end: false})
})
})
Edited to add: this has worked for me with small files. If I try testing it with a large test_image.jpg the request times out.
const testImage = `${__dirname}/../../../assets/test_image.jpg`
describe('Upload endpoint', () => {
test('Successfully uploads jpg image', async () =>
request(app)
.post(`${ROOT_URL}${endpoints.add_image.route}`)
.set('Authorization', `Bearer ${process.env.testUserJWT}`)
.attach("name",testImage,{ contentType: 'application/octet-stream' })
.expect(200)
.then(response => {
console.log("response",response);
})
);
});
I had to make assumptions about your upload method taking the body as input instead of multipart form-data. So below is an example where the raw body is passed for upload
const request = require('supertest');
const express = require('express');
const fs = require('fs')
const app = express();
var bodyParser = require('body-parser')
app.use(bodyParser.raw({type: 'application/octet-stream'}))
app.post('/user', function(req, res) {
res.status(200).json({ name: 'tobi' });
});
testImage = './package.json'
resp = request(app)
.post('/user')
resp.set('Authorization', `Bearer Test`).set('Content-Type', 'application/octet-stream')
resp.send(fs.readFileSync(testImage, 'utf-8'))
resp.expect(200)
.then(response => {
console.log("response",response);
}).catch((err) => {
console.log(err)
})
If you use multipart/form-data then below code shows an example
const request = require('supertest');
const express = require('express');
const fs = require('fs')
const app = express();
app.post('/user', function(req, res) {
// capture the encoded form data
req.on('data', (data) => {
console.log(data.toString());
});
// send a response when finished reading
// the encoded form data
req.on('end', () => {
res.status(200).json({ name: 'tobi' });
});
});
testImage = './package.json'
resp = request(app)
.post('/user')
resp.set('Authorization', `Bearer Test`)
resp.attach("file", testImage)
resp.expect(200)
.then(response => {
console.log("response",response);
}).catch((err) => {
console.log(err)
})
I think you actually want to use fs.createReadStream(testImage) to read that image into your request, since fs.createWriteStream(testImage) would be writing data into the file descriptor provided (in this case testImage). Feel free to checkout Node Streams to see how they work in more detail.
I'm not quite sure where you're getting the finish event from for supertest, but you can see how to use the .pipe() method here.
You might also want to consider using supertest multipart attachments, if that better suits your test.

Avoiding Mocha timeout on assertion error with SuperTest?

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.

Categories

Resources