BeforeAll in JEST not working as expected with asynchronous code - javascript

I'm trying to write few test cases using JEST for testing my API's. So I need the JWT token value in these cases. For getting the token value I've created an asynchronous getJWTToken function. Now I'm calling this function inside beforeAll().
But I'm getting random results on running the test cases. Most of the times all test cases are getting passed successfully but few of the times I'm getting 403 forbidden error as before getting the Token value my test cases started processing.
Could someone suggest any workaround for this.?
import app from '../../../src/app';
const { getJWTToken } = require('../../../src/utils/app.util');
const should = require('should');
const request = require('supertest')(app);
describe('API Test Cases :', () => {
const body = {};
beforeAll(async () => {
const data = await getJWTToken();
body['user_id'] = data[0]['_id'];
body['token'] = data[0]['token'];
});
describe('API 1', () => {
it('Should fetch', (done) => {
request.post('endpoint')
.send({
requestBody
})
.set('Accept', 'application/json')
.set('Authorization', `bearer ${body['token']}`)
.expect(commonAssertions)
.end((err, res) => {
if (err) return done(err);
return done();
});
});
});
}

Related

How to unit test an Express Controller with Jest

I have been getting terribly confused with how to test my controller functions. I realize that I have to mock my dependencies, request, response, and the controller functions. Here's what I have so far:
OrdersController.js
const OrderService = require('../services/orderServices')
module.exports = class OrdersController {
static async apiGetOrders(req, res, next) {
try {
const orders = await OrderService.getOrders()
return res.status(200).json(orders)
} catch (error) {
return res.status(500).json({ error: 'Unable to get orders' }) // 500, Internal Service Error, generic
}
}
static async apiPostOrder(req, res, next) {
// All good, create an orderDocument
try {
const orderDocument = {
_id: null, // undefined at this point Mongo creates this _id for us
orderId: req.body.orderId,
cookies: req.body.cookies,
daySelected: req.body.daySelected,
timeSelected: req.body.timeSelected,
userInfo: req.body.userInfo,
createdAt: new Date(),
}
await OrderService.addOrder(orderDocument)
return res.status(201).send('success') // status OK, something was Created
} catch (error) {
return res.status(500).json({ error }) // 500, Internal Server Error
}
}
OrdersController.spec.js
import OrderService from '../services/orderServices'
import { mockOrder, mockOrders } from '../mocks/fixtures'
import OrdersController from '../controllers/ordersController'
jest.mock('../controllers/ordersController.js')
const mockRequest = () => {
return {}
}
const mockResponse = (mockOrders) => {
const res = {};
res.status = jest.fn().mockReturnValue(200);
res.json = jest.fn().mockReturnValue(mockOrders);
return res;
}
// #3 Test the OrdersControllers
// mock dependencies: req, res, and spyOn the controller functions
describe('Orders Controller', () => {
test('[Positive], should call OrderService.getOrders and receive status 200', async () => {
jest.spyOn(OrdersController, 'apiGetOrders')
const req = mockRequest()
const res = mockResponse(mockOrders)
await OrdersController.apiGetOrders(req, res)
expect(res.status).toHaveBeenCalledWith(200)
expect(res.json()).toEqual(mockOrders)
})
test('[Negative], error yields status 500', async () => {
jest.spyOn(OrdersController, 'apiGetOrders')
const req = mockRequest()
const res = mockResponse({status: 500, error: 'Unable to get orders'})
await OrdersController.apiGetOrders(req, res)
expect(res.status).toHaveBeenCalledWith(500)
expect(res.json()).toEqual(error)
})
})
I'm trying to test the happy path and the negative path on the get request. I followed this expample, https://codewithhugo.com/express-request-response-mocking/, and read all of the jest docs, https://jestjs.io/docs/mock-functions. The error that I receive is:
Questions:
Am I actually writing the tests correctly?
Am I also supposed to mock the OrderService?
Should I use Sinon or is Jest more than sufficient?
I am also new to Jest and am struggling with finding documentation that goes to enough detail to suggest to me what I'm doing wrong. But in your case, I think you might be spying on the wrong thing. The OrdersController is the subject of the test, so I don't believe that you should be mocking that. Rather you should spy on OrdersController's dependency, Orderservice and mock its methods.
Also, its not clear to me why you have next in
static async apiGetOrders(req, res, next)
You don't seem to use it in the body of the method anywhere, so hanging any testing off that value, probably won't work either.

How do I call Airtable rest API from inside an AWS Lambda?

I am trying to call my rest api endpoint in AIRTABLE from inside an AWS Lambda with no success. I get no errors, no outputs.
If I call the same code using node - it works.
I am able to use Axios in my code.
Pure airtable code (works)
var Airtable = require('airtable');
var base = new Airtable({apiKey: 'keyoMYSECRETKEY'}).base('Mybaseid');
base('MyBase').select({maxRecords: 3,view: "MyView"}).eachPage(function page(records, fetchNextPage) {
// This function (`page`) will get called for each page of records.
records.forEach(function(record) {
console.log('Retrieved',JSON.stringify(record.get('Session Information')));
});
fetchNextPage();
}, function done(err) {
if (err) { console.error(err); return; }
});
If I put it inside a Lambda handler - I get nothing.
const axios = require('axios')
const url = 'https://checkip.amazonaws.com/';
var Airtable = require('airtable');
var base = new Airtable({apiKey: 'keySECRETKEY'}).base('MYBASEID');
let response;
exports.lambdaHandler = async (event, context) => {
try {
base('MyBase').select({maxRecords: 3,view: "MyView"}).eachPage(function page(records, fetchNextPage) {
records.forEach(function(record) { //HERE - NOTHING HAPPENS
console.log('Retrieved',JSON.stringify(record.get('Session Information')));
});
fetchNextPage();
}, function done(err) {
if (err) { console.error(err); return; }
});
const ret = await axios(url); //THIS WORKS
response = {
'statusCode': 200,
'body': JSON.stringify({
message: 'hello world - boo',
location: ret.data.trim()
})
}
} catch (err) {
console.log(err);
return err;
}
return response
};
What am I missing so I can call Airtable API from inside an AWS Lambda?
It seems that your lambda terminates before the API call execution your trying to perform.
I believe this will be solved using a synchronous lambda or with a correct usage of promises with await calls.
Best way to troubleshoot this is to go back to the basics.
See if you can at least get a meaningful console log by wrapping a simpler fetch request into a lambda handler:
const baseId = 'exampleAppId123';
const tableName = 'Table 1';
const api_key = 'keyExample123';
const url = `https://api.airtable.com/v0/${baseId}/${tableName}?api_key=${api_key}`;
exports.lambdaHandler = async () => {
const res = await fetch(url)
.then(res => res.json())
.then(data=>console.log(data))
.then(() => {
//do more stuff
})
}
Then report back if you can't. Or better yet, report back either way as that's bound to help more people in the future.
Worst case? The above code still doesn't do anything. If that happens, I suggest going with #Shoty's first instinct and turning this code into a synchronous fetch request by removing the async/await syntax and returning chained thenables. Not that blocking behavior of this sort is acceptable from a UX perspective, but it should at least help with debugging.

Nock is not intercepting request if it runs multiples test in certain order

I'm creating some unit test for my endpoint. This endpoint will fetch data from an external API and send diff responses depending on finding a certain item on that data fetched or if there is an error fetching data from that external API.
I have 2 unit test, one makes a call to the external API and the other I mock it with nock to intercept that external API call and send back an error. The problem: When I run both inside describe, the one which I used nock fails. If I run both test separated, they both succeed. If I reorder the unit test, putting the one with the nock first and the other second, they both pass. I read and tried some solutions related to nock and didn't work.
here is the unit test:
describe("x endpoint testing", () => {
afterEach("Restore Nocks", async (done) => {
if (nock.isActive()) {
nock.cleanAll();
nock.restore();
}
done();
});
it("should return 200", (done) => {
chai
.request(server)
.get(`/endpoint/${realParams}`)
.set("Content-Type", "application/json")
.send()
.then((res) => {
res.should.have.status(200);
done();
})
.catch(done);
});
it("should return 400 if a connection error occur with API", (done) => {
if (!nock.isActive()) nock.activate();
const apiNock = nock(process.env.API_ENDPOINT)
.log(console.log)
.get(`/api`)
.replyWithError({
message: "Something awful happened",
code: "404",
});
chai
.request(server)
.get(`/endpoint/${fakeParams}`)
.set("Content-Type", "application/json")
.send()
.then((res) => {
expect(apiNock.isDone()).to.be.true;
res.should.have.status(400);
res.body.should.have.property("error");
done();
})
.catch(done);
});
});
I usually prefer to outline exactly what the issues are in your example, however, I think issues lay outside the provided code. Instead, here is some code that runs and passes no matter which order the tests are run.
My hope is that you can compare this with the rest of your project and easily identify what is amiss.
const chai = require("chai")
const chaiHttp = require('chai-http')
const nock = require("nock")
const http = require("http")
const API_ENDPOINT = 'http://postman-echo.com'
chai.use(chaiHttp);
const server = http.createServer((request, response) => {
const outReq = http.get(`${API_ENDPOINT}/get?foo=${request.url}`, () => {
response.end()
});
outReq.on("error", err => {
response.statusCode = 400;
response.write(JSON.stringify({error: err.message}))
response.end()
})
});
describe("endpoint testing", () => {
afterEach((done) => {
nock.cleanAll();
done();
});
it("should return 200", (done) => {
chai
.request(server)
.get(`/endpoint/live`)
.set("Content-Type", "application/json")
.send()
.then((res) => {
chai.expect(res.status).to.equal(200);
done();
})
.catch(done);
});
it("should return 400 if a connection error occur with API", (done) => {
const apiNock = nock(API_ENDPOINT)
.get('/get?foo=/endpoint/fake')
.replyWithError({
message: "Something awful happened",
code: "404",
});
chai
.request(server)
.get(`/endpoint/fake`)
.set("Content-Type", "application/json")
.send()
.then((res) => {
chai.expect(apiNock.isDone()).to.be.true;
chai.expect(res.status).to.equal(400);
chai.expect(res.text).to.equal('{"error":"Something awful happened"}');
done();
})
.catch(done);
});
});

Test the status code of a real request to an API with Jest

Hello I'm trying to test this API call but I don't know how to test for the status code of the response since it is a real (and it has to stay like that) API call and not a mock one
this is the function I'm testing:
export const getDataFromApi = (url) => {
return axios.get(url)
.then(({ data }) => data)
.catch(err => console.log(err.toString()));
}
and this is the test:
describe('Read data from API', () => {
test('Get result of the API call', (done) => {
const apiUrl = "https://rickandmortyapi.com/api/character";
getDataFromApi(apiUrl)
.then(data => {
expect(data).toBeDefined();
expect(data.results.length).toBeGreaterThan(0);
done();
});
});
});
how can I expect if the status code of data is 200 or if is another status code?
also is necessary for me to leave that done after the execution of the function? I know with call backs I have to put it but with this promise I'm not sure
Axios has a single response object returned in both the success and error paths which contains the HTTP status code. An error is raised if the response is not in the 2xx range.
You can plumb the status code as a return object from your getDataFromApi() wrapper function, but you'll probably want the full response object for other checks (like headers). I recommend getting rid of the wrapper altogether.
Without the wrapper, here's 2 different status checks using promises, one for success and one for failure:
describe('Read data from API', () => {
test('Get successful result of the API call', async() => {
const apiUrl = "https://rickandmortyapi.com/api/character";
await axios.get(apiUrl)
.then(r => {
expect(r.data).toBeDefined();
expect(r.data.results.length).toBeGreaterThan(0);
expect(r.status).toBeGreaterThanOrEqual(200);
expect(r.status).toBeLessThan(300);
})
.catch(e => {
fail(`Expected successful response`);
});
});
test('Get failure result of the API call', async() => {
const apiUrl = "https://rickandmortyapi.com/api/character-bad";
await axios.get(apiUrl)
.then(r => {
fail(`Expected failure response`);
})
.catch(e => {
if (e.response) {
expect(e.response.status).toBeGreaterThanOrEqual(400);
expect(e.response.status).toBeLessThan(500);
} else {
throw e;
}
});
});
});

How to properly test an Express controller method with Mocha, Chai, and Sinon

I'm pretty new to using Sinon. I have the following test I've written, and it fails because res.status always comes back as not called.
import chai from 'chai';
import 'chai/register-should';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { db } from '../../models';
import * as loginController from '../../controllers/login';
chai.use(sinonChai);
describe('Login controller', () => {
describe('post function', () => {
let findOne, req, status, send, res;
beforeEach(() => {
findOne = sinon.stub(db.User, 'findOne');
findOne.resolves(null);
req = { body: { email: 'test#test.com', password: 'testpassword' }};
status = sinon.stub();
send = sinon.spy();
res = { send: send, status: status };
status.returns(res);
loginController.post(req, res);
});
afterEach(() => {
findOne.restore();
});
it('should return a 401 status for an invalid email', (done) => {
res.status.should.be.calledWith(401);
findOne.restore();
done();
});
});
});
The method in the controller right now is pretty simple. It uses a sequelize findOne method first. If it doesn't find a matching email it should throw a 401. Here's what that looks like:
export function post(req,res) {
const email = req.body.email;
const password = req.body.password;
db.User.findOne({
where: {email: email}
}).then(user => {
if (user) {
// Other stuff happens here
} else {
res.status(401).send('That email address does not exist in our system.');
}
}).catch((error) => {
res.status(500).send(error.message);
});
}
When I run the test it does get to the else statement where it should be returning the status, but the test fails and when I check the log it says that the res.status wasn't ever called.
The problem here is that the spec is synchronous and doesn't take a promise into account.
It makes sense to return a promise for testability reasons:
export function post(req,res) {
...
return db.User.findOne(...)
...
}
This can be naturally done if route handler is async function.
Since Mocha supports promises, the specs can use async functions instead of done callback as well:
it('should return a 401 status for an invalid email', async () => {
const handlerResult = loginController.post(req, res);
expect(handlerResult).to.be.a('promise');
await handlerResult;
res.status.should.be.calledWith(401);
});

Categories