1

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); }); 
1
  • How do i present more details when i dont know where to start? Commented Aug 23, 2021 at 14:16

1 Answer 1

2

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": [] } }) 
Sign up to request clarification or add additional context in comments.

5 Comments

thank you for this amazing explanation, it does explain me about dealing with dynamodb, and i would like to go with mock rather than running a local version. can you explain me by providing some example code based on my code however can you explain me how do i mock the existing dynamodb function call in the code which i have presented above. also updated my test code too,
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. -> i have deployed my api using serverless which doesnt have any config paramters for dynamodb, in short how do i refactor my ecisting code to succesfully mock it
I've added a small example. Since we can stub on the prototype for the DynamoDB DocumentClient, we don't need any refactoring. But be aware that this is not possible for every class in the aws-sdk. (PS, if this answers your question, please mark the response as accepted).
if you noticed i have seperate test.js and seperate router file, can you also explain me how do i implement your provided example, should it be in test.js or my router file. i am confused that how you example part will replace implementation which is present in router file
The example is - for brevity - in one file, but you should split that up into two files. The lines where you actually create the stub should be in your test file.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.