How to test api route in nextjs with Jest - jestjs

I have a function in my api folder that searches an array of services and returns a single service depending on the slug.
The api function is:
import {services} from '../../../public/data/service.js'
export default async function getSingleService (req, res) {
const serviceId = req.query.serviceID
const result = services.filter((each) => each.url === serviceId )
if(result.length > 0) {
return res.status(200).json(result[0])
} else {
res.status(404).json({message: 'Service not found'})
}
}
How do I got about to write a test for this function. I just started with jest, any help would be nice.

Not sure if you're still looking for an answer, but this seems to work for me. It works on the default "hello" route. I found it here
import { createMocks } from 'node-mocks-http';
import handler from '../../../pages/api/hello';
describe('/api/hello', () => {
test('should returna an object with a name', async () => {
const { req, res } = createMocks({
method: 'GET'
});
await handler(req, res);
expect(res._getStatusCode()).toBe(
200
);
expect(
JSON.parse(res._getData())
).toHaveProperty('name');
});
});

Related

How can I test authentication middleware with Jest

I'm learning nodejs and for the most part its going well. Im trying to learn how to do mocking in tests with jest. I've watched numerous tutorials but I cant seem to get my head around it.
I have this middleware that is used on protected routes...
import jwt from 'jsonwebtoken';
export default function (req, res, next) {
const token = req.header('x_auth-token');
if (!token) return res.status(401).json({ message: 'Access denied' });
try {
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
req.user = verified;
next();
} catch (err) {
return res.status(400).send('Invalid Token');
}
}
From what I've read, I think the approach Im supposed to to take is something like this...
import verifyToken from '../middleware/verifyToken';
test('verifyToken', () => {
expect.assertions(1);
const res = {};
const req = {};
const next = (err) => expect(err).toBeFalsy();
verifyToken(req, res, next);
});
However this clearly doesnt work.
So how do I mock the request header with a token?
So if we comletely forget about what req, res are in the real world, obviously they are request and response, but lets just forget about that for now.
in your real code you have token = req.header("x_auth-token")
So in our test code, we need to have something in the req object that when called with those parameters returns what you want.
So I would say.
const req = {
header: jest.fn(() => 'myAuthToken')
}
the jest.fn() has mad a mock function and when it is invoked it will always return the string myAuthToken.
We can then check that the header function has been called with the correct params by adding
expect(req.header).toHaveBeenCalledWith("x_auth-token")
You are also going to need to mock jwt.verify as you are not testing that jwt.verify works as that will be covered it its own tests. You will want to make sure that you are using the response of that correctly
To do that take a look at this stack overflow question
And finally I would set next as a mock function
mockNext = jest.fn()
Then we can say in the test
expect(mockNext).toHaveBeenCalled()
So I was having trouble understandin how to mock functions. I did a bunch of googling based on on Ollie Pugh's answer and this is what I came up with.
import jwt from 'jsonwebtoken';
import verifyToken from '../middleware/verifyToken';
import { uIds } from './testdata/userTestData';
describe('verifyToken tests', () => {
const { uid1 } = uIds;
it('Should pass the userId to the request object if token is verified', () => {
const res = {};
const req = {
header: jest.fn(() => 'myAuthToken'),
};
const next = jest.fn();
const verify = jest
.spyOn(jwt, 'verify')
.mockReturnValueOnce({ userId: String(uid1) });
verifyToken(req, res, next);
expect(req.header).toHaveBeenCalledWith('x_auth-token');
expect(req.user).toEqual({ userId: String(uid1) });
expect(next).toHaveBeenCalled();
});
it('Should deny access if token is not present in header', () => {
const res = {
json(msg) {
expect(msg).toEqual({ message: 'Access denied' });
},
status(responseStatus) {
expect(responseStatus).toEqual(401);
return this;
},
};
const req = {
header: jest.fn(),
};
const next = jest.fn();
verifyToken(req, res, next);
expect(req.header).toHaveBeenCalledWith('x_auth-token');
expect(req.user).not.toEqual({ userId: String(uid1) });
});
it('Should deny access if token is invalid', () => {
const res = {
send(text) {
expect(text).toEqual('Invalid Token');
},
status(responseStatus) {
expect(responseStatus).toEqual(400);
return this;
},
};
const req = {
header: jest.fn(() => 123),
};
const next = jest.fn();
const verify = jest.fn();
verifyToken(req, res, next);
expect(req.header).toHaveBeenCalledWith('x_auth-token');
expect(req.user).not.toEqual({ userId: String(uid1) });
});
});
This passes the tests. However I'm not sure about the validity of the test.

Offline Jest unit test implementation in firebase functions in Nodejs

I am trying to implement jest unit testing for firebase functions in my application. I was purely looking at implementing an offline test. I have mocked the request and response objects and am still trying to access some other firebase resources on execution. I am not sure whether I missed any sort of mocking. I intend to not use any firebase-related configuration URLs.
Here is my function and test samples,
randomgenerator.ts
import { https } from "firebase-functions";
export const generateTrickyNumber = https.onRequest(async (req, res) => {
corsHandler(req, res, async () => {
try {
let num =0;
switch (req.body.type) {
case "palindrome":
num= await generatePalindrome(req.body.startnumber,req.body.endnumber);
break;
case "prime":
num= await generatePrime(req.body.startnumber,req.body.endnumber);
break;
default:
break;
}
return res.status(200).send(num);
} catch (err) {
console.log({ err });
return;
}
});
});
randomgenerator.test.ts
import 'jest';
import * as httpMock from 'node-mocks-http';
import * as TestFunctions from 'firebase-functions-test';
const testEnv = TestFunctions();
import { generateTrickyNumber } from "../src/randomgenrator.ts";
describe('generateTrickyNumber ', () => {
test('it returns a successful response with a valid card', async () => {
const req = { method: "POST", body: {type:"palindrome", startnumber:1,endnumber:200} };
const res = {
send: (payload) => {
expect(payload).toBe(153)
},
};
await generateTrickyNumber (req as any, res as any);
});
test('it returns an error if the method is not passed', async () => {
const req = { body: { type: "palindrome" } };
const res = {
send: (payload) => {
expect(payload).toBe('Missing value!')
},
};
await generateTrickyNumber (req as any, res as any);
});
});
Do we need to handle any more mocking for making this successful? Please feel free to suggest.

Jest/SuperTest Express integration tests - Can't set headers after they are sent. (when you call the same endpoint in multiple tests)

This one's killing me..
I'm writing integration tests for an Express (Typescript) app, using Jest and Supertest.
I have multiple tests for the same endpoint, to test responses from when a mocked service returns data correctly and when it rejects a promise with an Error object.
The tests run fine when each request() in each it() block hits a unique end point, but when endpoints are shared between blocks I get the following error:
Can't set headers after they are sent.
This is my test code:
let request = null;
let app = null;
const async = require('async');
import GError from '../../src/services/ErrorService';
const { list } = require('../../src/controllers/RecipeController');
let throwError: boolean = false;
let error = null;
const errorMsg: string = 'Something went wrong!';
const listData: Array<object> = [{id: 1, base: 'something'}];
jest.mock('../../src/services/RecipeService', () => {
return jest.fn().mockImplementation(() => ({
list: jest.fn(() => {
if (throwError) {
return Promise.reject(error);
}
return Promise.resolve(listData);
})
}));
});
beforeEach(() => {
request = require('supertest');
app = require('../../src/app');
});
afterEach( ( done ) => {
throwError = false;
error = null;
app.close( () => {
delete require.cache[require.resolve('../../src/app')];
done();
});
});
describe('Root Path', () => {
it('should return a welcome message', (done) => {
request(app)
.get('/')
.end((err, res) => {
expect(res.text).toEqual('Test API.');
expect(res.statusCode).toBe(200);
done();
});
});
});
describe('Recipe List', () => {
it('should call controller and return correct response when successful or error is thrown in service', (done) => {
const path: string = '/recipes/list';
request(app)
.get(path)
.end((err, res) => {
expect(JSON.parse(res.text)).toEqual({
recipes: listData
});
done();
});
});
it('should return an error response if service rejects promise', (done) => {
throwError = true;
error = new GError(errorMsg);
const path: string = '/recipes/list';
request(app)
.get(path)
.end((err, res) => {
expect(JSON.parse(res.text)).toEqual({
errors: {
message: errorMsg
}
});
done();
});
});
});
I think I need to reset the app in between tests, which is what I'm trying to achieve with:
beforeEach(() => {
request = require('supertest');
app = require('../../src/app');
});
But with no joy. Can anyone shine a light?
UPDATE:
Here's the controller method the route hits:
exports.list = async (req, res, next) => {
const recipes: IRecipeList = await recipeService.list().catch(err => {
return next(err);
});
const response: IRecipeListResponse = {recipes};
res.status(200).json(response);
};
SOLVED:
So it turned out to be nothing to do with Jest / Superagent (I was sure it was to do with one of these). Strangely though I only get this error in the context of running integration tests, there is no error when hitting the end point in Postman - which was super confusing.
PROBLEM:
exports.list = async (req, res, next) => {
const recipes: IRecipeList = await recipeService.list().catch(err => { . //this doesn't stop the execution past this await
return next(err);
});
//this still gets processed if the service rejects the promise
const response: IRecipeListResponse = {recipes};
res.status(200).json(response);
};
SOLUTION:
exports.list = async (req, res, next) => {
let error = false;
const recipes: IRecipeList = await recipeService.list().catch(err => {
error = true;
return next(err);
});
if (error) {
return;
}
const response: IRecipeListResponse = {recipes};
return res.status(200).json(response);
};
This error occurs when you send response more than once.

Mock static methods es6 classes to test

I have a lib say 'services.js'
class Service {
static doSomething() {
return Promise.resolve({});
}
}
I have another handler 'handler.js'
let Service = require('./Service');
exports.search = (req, res) => {
Service.doSomething().then(result => {
res.send(result);
}).catch(err=>{
res.status(500).send(err);
});
}
I want to test my handler. To do so I tried stubbing the static method in Service class like:
let Service = require(path to services.js),
Handler = require(path to handler.js),
http_mocks = require('node-mocks-http'),;
describe("handler tests : ", () => {
before(()=>{
sinon.stub(Service, 'doSomething').callsFake(()=>{});
})
it('should succeed', (done) => {
let response = buildResponse();
let request = http_mocks.createRequest({
method: 'GET',
url: '/search?q=2',
});
response.on('end', function() {
let result = JSON.parse(response._getData());
//Some validation
done();
});
Handler.search(request, response);
done();
})
})
I get TypeError: Service.doSomething is not a function. Is there an alternative? I tried using mockery as well. Am I missing something
I found a bug in the test code. Service.doSomething() returns promise but in the test file, we stub it using callsFake and it doesn't return promise
We might use resolves for this as in
before(() => {
sinon.stub(Service, 'doSomething').resolves('asik');
});
NOTE: resolves has been supported since Sinon 4.

How to test express middleware that depends on other vendor middleware?

I wanna test a middleware function that inside calls a vendor middleware function. The middleware is:
const expressJwt = require('express-jwt');
const validateJwt = expressJwt({ secret: 'whatever' });
exports.isAuthenticated = (req, res, next) => {
if (req.query && req.query.hasOwnProperty('access_token')) {
req.headers.authorization = `Bearer ${req.query.access_token}`;
}
validateJwt(req, res, next);
};
I've tried to create a sinon.spy() object and pass it as next parameter, but is not called apparently.
Another approach I've tried is to check if exists req.user, since the purpose of the express-jwt middleware is to validate and attach user to the req object. No luck with this neither.
I've seen the existence of chai-connect, but not sure how to use it.
Any ideas? Highly appreciate it!
I finally managed to do it with proxyquire and chai-connect:
In your mocha config:
chai.use(require('chai-connect-middleware'));
global.connect = chai.connect;
In your test:
describe('isAuthenticated', () => {
// Wrap call to chai.connect into a function to use params and return a Promise
const mockedMiddleware = (changeSecret) => {
let oldToken;
if (changeSecret) {
oldToken = acessToken;
acessToken = 'blabalblalba';
}
return new Promise((resolve, reject) => {
connect.use(middleware.isAuthenticated)
.req(req => {
req.query = { access_token: acessToken };
})
.next((res) => {
acessToken = oldToken;
if (res && res.status === 401) {
reject(res.message);
} else {
resolve();
}
})
.dispatch();
});
};
it('should validate correctly', () =>
mockedMiddleware().should.be.fulfilled
);
it('should not validate', () =>
mockedMiddleware(true).should.be.rejected
);
});

Resources