Override Prerequisite in HapiJS Unit Test - unit-testing

I'm trying to write test for a route that makes an external API call in the prerequisite. In the test I'm writing I don't want that call to be made but I want to make use of a stub to simulate the call.
How do I override the prerequisite from my test?

Have you tried Shot? I haven't either, but it injects a fake HTTP request/response into a node HTTP server for simulating server logic, writing tests, or debugging.
Before your tests run, you could mock the requests you expect to be made, and specify the expected result.
const Http = require('http');
const Shot = require('shot');
(function () {
const dispatch = function (req, res) {
const reply = 'Hello World';
res.writeHead(200, {
'Content-Type': 'text/plain',
'Content-Length': reply.length
});
res.end(reply);
};
const server = Http.createServer(dispatch);
Shot.inject(dispatch, { method: 'get', url: '/api/hello' }, (res) => {
console.log(res.payload);
});
})()

Related

Postman test script - how to call an api twice to simulate 409 error

I am trying to run a few automated testing using the Postman tool. For regular scenarios, I understand how to write pre-test and test scripts. What I do not know (and trying to understand) is, how to write scripts for checking 409 error (let us call it duplicate resource check).
I want to run a create resource api like below, then run it again and ensure that the 2nd invocation really returns 409 error.
POST /myservice/books
Is there a way to run the same api twice and check the return value for 2nd invocation. If yes, how do I do that. One crude way of achieving this could be to create a dependency between two tests, where the first one creates a resource, and the second one uses the same payload once again to create the same resource. I am looking for a single test to do an end-to-end testing.
Postman doesn't really provide a standard way, but is still flexible. I realized that we have to write javascript code in the pre-request tab, to do our own http request (using sendRequest method) and store the resulting data into env vars for use by the main api call.
Here is a sample:
var phone = pm.variables.replaceIn("{{$randomPhoneNumber}}");
console.log("phone:", phone)
var baseURL = pm.variables.replaceIn("{{ROG_SERVER}}:{{ROG_PORT}}{{ROG_BASE_URL}}")
var usersURL = pm.variables.replaceIn("{{ROG_SERVICE}}/users")
var otpURL = `${baseURL}/${phone}/_otp_x`
// Payload for partner creation
const payload = {
"name": pm.variables.replaceIn("{{username}}"),
"phone":phone,
"password": pm.variables.replaceIn("{{$randomPassword}}"),
}
console.log("user payload:", payload)
function getOTP (a, callback) {
// Get an OTP
pm.sendRequest(otpURL, function(err, response) {
if (err) throw err
var jsonDaata = response.json()
pm.expect(jsonDaata).to.haveOwnProperty('otp')
pm.environment.set("otp", jsonDaata.otp)
pm.environment.set("phone", phone);
pm.environment.set("username", "{{$randomUserName}}")
if (callback) callback(jsonDaata.otp)
})
}
// Get an OTP
getOTP("a", otp => {
console.log("OTP received:", otp)
payload.partnerRef = pm.variables.replaceIn("{{$randomPassword}}")
payload.otp = otp
//create a partner user with the otp.
let reqOpts = {
url: usersURL,
method: 'POST',
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify(payload)
}
pm.sendRequest(reqOpts, (err, response) => {
console.log("response?", response)
pm.expect(response).to.have.property('code', 201)
})
// Get a new OTP for the main request to be executed.
getOTP()
})
I did it in my test block. Create your normal request as you would send it, then in your tests, validate the original works, and then you can send the second command and validate the response.
You can also use the pre and post scripting to do something similar, or have one test after the other in the file (they run sequentially) to do the same testing.
For instance, I sent an API call here to create records. As I need the Key_ to delete them, I can make a call to GET /foo at my API
pm.test("Response should be 200", function () {
pm.response.to.be.ok;
pm.response.to.have.status(200);
});
pm.test("Parse Key_ values and send DELETE from original request response", function () {
var jsonData = JSON.parse(responseBody);
jsonData.forEach(function (TimeEntryRecord) {
console.log(TimeEntryRecord.Key_);
const DeleteURL = pm.variables.get('APIHost') + '/bar/' + TimeEntryRecord.Key_;
pm.sendRequest({
url: DeleteURL,
method: 'DELETE',
header: { 'Content-Type': 'application/json' },
body: { TimeEntryRecord }
}, function (err, res) {
console.log("Sent Delete: " + DeleteURL );
});
});
});

How to get code coverage metrics from integration tests using serverless-offline and Supertest?

I'm building an AWS Lambda function and trying to write some integration tests for it. The Lambda function is running locally using serverless-offline plugin and simply receive a GET request with some query parameters. I'm using Jest and Supertest to write my integration tests as follow:
import request from 'supertest';
describe('User position handler', () => {
it('should return history', () => {
const server = request('http://0.0.0.0:4000');
return server
.get(`/local/position?type=month&period=3`)
.expect(200)
.expect((response) => {
console.log('RESPONSE', response);
expect(response.body).toHaveLength(3);
});
});
});
The problem is that when I run Jest with collect coverage option the code reached by the request sent with Supertest is not computed in the metrics. Running jest --collectCoverage the result is:
The question is that I know that, for example, infra/handlers/user-position.ts is being reached and covered more than 0% statements, but the coverage metrics don't show as expected. Also, I know that user-monthly-position.service.impl.ts is being reached at some point of the flow since this service is responsible for returning data from an external service and the response from Supertest is returning data. The green lines are from files covered by unit test that are using only Jest (and not Supertest, obviously)
I know that when using Supertest with Express framework I can pass an instance of the Express app. to the request function. This way I think that Jest can "inspect" or "instrument" the function call stack to measure the coverage (code sample below). But how can I do the same passing the URL of a running serverless-offline Lambda?
const request = require('supertest');
const assert = require('assert');
const express = require('express');
const app = express();
app.get('/user', function(req, res) {
res.status(200).json({ name: 'john' });
});
request(app)
.get('/user')
.expect('Content-Type', /json/)
.expect('Content-Length', '15')
.expect(200)
.end(function(err, res) {
if (err) throw err;
});
Here the code of my handler function:
export default async (event: APIGatewayEvent): Promise<APIGatewayProxyResult> => {
await cacheService.bootstrapCache();
const userMonthlyPositionService = new UserMonthlyPositionServiceImpl(
cacheService.connectionPool,
);
const getUserMonthlyPositionHistory = new GetUserMonthlyPositionHistory(
userMonthlyPositionService,
);
const result = await getUserMonthlyPositionHistory.execute({
cblc: 999999,
period: 3,
type: 'month',
});
return buildResponse(200, result);
};
My question is: how can I collect right code coverage metrics from Jest using Supertest and Serverless Framework? Am I forgetting a detail? Thanks!

How to test express + dynamodb using Jest?

I am new to Jest and unit testing, I have an express API deployed on serverless(Lambda) on AWS.Express api uses dynamodb for crud operations
Note:- my api is based out of express and not just plain node, because on jest website they are telling ways for plain nodejs
I am able to do unit test on express on the methods which doesnt use dynamodb.However it fails for the methods which are using dynamodb, as to my understanding this has something to do with dynamodb being remote, because the code present in app.js corresponds to dyanmo db which is hosted on aws using lambda.
How do I go about it?
Note:- my api is based out of express and not just plain node
const isUrl = require('is-url');
const AWS = require('aws-sdk');
const { nanoid } = require('nanoid/async');
const express = require('express');
const router = express.Router();
const dynamoDb = new AWS.DynamoDB.DocumentClient();
// URL from users
router.post('/', async (req, res, next) => {
// urlId contains converted short url characters generated by nanoid
const urlId = await nanoid(8);
const { longUrl } = req.body;
// Veryfying url Format using isUrl, this return a boolean
const checkUrl = isUrl(longUrl);
if (checkUrl === false) {
res.status(400).json({ error: 'Invalid URL, please try again!!!' });
}
const originalUrl = longUrl;
const userType = 'anonymous'; // user type for anonymous users
const tableName = 'xxxxxxxxxxxxx'; // table name for storing url's
const anonymousUrlCheckParams = {
TableName: tableName,
Key: {
userId: userType,
originalUrl,
},
};
dynamoDb.get(anonymousUrlCheckParams, (err, data) => {
const paramsForTransaction = {
TransactItems: [
{
Put: {
TableName: tableName,
Item: {
userId: userType,
originalUrl,
convertedUrl: `https://xxxxxxxxxxxxxxxx/${urlId}`,
},
},
},
{
Put: {
TableName: tableName,
Item: {
userId: urlId,
originalUrl,
},
ConditionExpression: 'attribute_not_exists(userId)',
},
},
],
};
if (err) {
console.log(err);
res
.status(500)
.json({ error: 'Unknown Server Error, Please Trimify Again!' });
} else if (Object.keys(data).length === 0 && data.constructor === Object) {
dynamoDb.transactWrite(paramsForTransaction, async (error) => {
if (error) {
// err means converted value as userId is repeated twice.
console.log(error);
res
.status(500)
.json({ error: 'Unknown Server Error, Please trimify again. ' });
} else {
res.status(201).json({
convertedUrl: `https://xxxxxxxxxxxx/${urlId}`,
});
}
});
} else {
res.status(201).json({
convertedUrl: data.Item.convertedUrl,
});
}
});
});
module.exports = router;
my test.js
const request = require('supertest');
const app = require('../app');
test('Should convert url from anonymous user ', async () => {
await request(app)
.post('/anon-ops/convert')
.send({
longUrl: 'https://google.com',
})
.expect(201);
});
First off, if you're wanting to do unit testing. It doesn't really matter much if you're using express js or not, hence, the examples and information on the jest website are very valid to get you on your way.
How easy it is to do unit testing, mostly depends on how you have structured your code. For example, you could keep all your express js specific code in separate files and then only instantiate the files holding your actual business logic (which some might call a services layer) during your unit tests. That's at least one way where you could make it easier on yourself. Using a functional approach also makes your code easier to test or at the very least using dependency injection, so you can swap out dependencies during testing in order to test some functionality in isolation.
When it comes to DynamoDB, you've got two options. Either mocking or running a local version.
You can either mock the specific functions you're calling either using the jest mocks or using a mocking library such as sinon. Whichever you choose is mostly personal preference.
The second option is running a local version of DynamoDB in a docker container. This has the upside of also verifying your actual calls to the DynamoDB service (which you could do by verifying the mocks, but it's easy to make a mistake in the verification), however, it is more cumbersome to set up and your tests will be slower, so this might skew your test to be more integration tests than unit tests (but that distinction is an evening worth or arguing in itself).
If you want to go towards end-to-end testing of the entire API, you can have a look at the SuperTest NPM package.
(Edit) Added small example using sinon
const AWS = require('aws-sdk');
const sinon = require('sinon');
const ddb = new AWS.DynamoDB.DocumentClient();
const getStub = sinon.stub(AWS.DynamoDB.DocumentClient.prototype, "get");
getStub.callsFake((params, cb) => {
cb(null, {result: []});
});
ddb.get({foo: 'bar'}, (err, val) => {
console.log(val); // => { "result": [] }
})

Mock Firebase Admin Auth for Unit Testing Authenticated Routes

I'm using firebase-admin for authentication on my express backend. I have a middleware that checks if requests are authenticated.
public resolve(): (req, res, next) => void {
return async (req, res, next) => {
const header = req.header('Authorization');
if (!header || !header.split(' ')) {
throw new HttpException('Unauthorized', UNAUTHORIZED);
}
const token = header.split(' ')[1];
await admin.auth().verifyIdToken(token).then((decodedToken: any) => {
req.user = decodedToken;
next();
}).catch((error: any) => {
throw new HttpException(error, UNAUTHORIZED);
});
};
}
So far, I can only unit test my routes to make sure that they respond UNAUTHORIZED instead of NOT_FOUND.
it('GET /api/menu should return 401 ', done => {
const NOT_FOUND = 404;
const UNAUTHORIZED = 401;
supertest(instance)
.get('/api/menu')
.end((error, response: superagent.Response) => {
expect(response.status).not.toEqual(NOT_FOUND);
expect(response.status).toEqual(UNAUTHORIZED);
done();
});
});
But, I want to write more unit tests than this! I want to mock users so I can make AUTHORIZED requests! I want to use the type property I have on users to verify that users of a certain type can/cannot use certain routes. Does anyone have an idea of how I could do this with firebase-admin-node?
It looks like the firebase-admin-node repo generates tokens for unit tests here, but I'm not sure how I would apply that to my specific problem.

unit testing express route controller with multiple external API requests

in my unit tests I need to mock some http requests.
const getDashboardData = (req, res) => {
const activeListings = articlesLib.getLiveArticles(req.user.id)
const soldThisMonthPromise = articlesLib.getSoldArticles(req.user.id, {})
const userDrafts = articlesLib.getDrafts(req.user.id)
return Promise.all([activeListings, soldThisMonthPromise, userDrafts])
.then((data) => {
res.render(`${__dirname}/home`, {
viewData: data
})
})
}
router.get('/', getDashboardData)
Each request from articleLib has the same url, but uri is different. I wanted to mock it with nock, but it doesn't support multiple mock for the same base url. Is there any tool that mock a response when given http request is detected?