I have an endpoint in API Gateway that maps to a Lambda function in AWS. When writing test cases for the new handler function for the endpoint, I don't want the spec file to call the actual API or connect to DynamoDB. I tried adding sinon.stub
but it still calls connect to DynamoDB and the test case fails. I can't find where the stub goes wrong.
Handler.js:
saveUser(userName, logger) { const Item = { id: uuid.v4(), userName, ttl: parseInt(Date.now() / 1000) + 900 // expire the name after 15 minutes from now }; const params = { TableName: "my-table-name", Item }; logger.log(`Saving new user name to DynamoDB: ${JSON.stringify(params)}`); return new Promise(function(resolve, reject) { db.put(params, function(err, _) { if (err) { logger.exception(`Unable to connect to DynamoDB to create: ${err}`); reject({ statusCode: 404, err }); } else { logger.log(`Saved data to DynamoDB: ${JSON.stringify(Item)}`); resolve({ statusCode: 201, body: Item }); } }); }); }
Handler.spec.js:
import AWS from "aws-sdk"; const db = new AWS.DynamoDB.DocumentClient({ apiVersion: "2012-08-10" }); describe("user-name-handler", function() { const sandbox = sinon.createSandbox(); afterEach(() => sandbox.restore()); it("Test saveUser() method", async function(done) { const { saveUser } = userHandler; sandbox.stub(db, "put") .returns(new Promise((resolve, _) => resolve({ statusCode: 200 }))); try { const result = await saveUser("Sample User", { log: () => {}, exception: () => {} }); expect(result).to.be.equal({ data: "some data" }); done(); } catch (err) { console.log(err); done(); } }); });
mistake:
Error: Resolution method is overspecified. Specify a callback *or* return a Promise; not both.
I logged the err
object via the console and it gave me this error, which makes me think it's trying to connect to DynamoDB.
Error: connect ENETUNREACH 127.0.0.1:80 at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1144:16) { message: 'Missing credentials in config, if using AWS_CONFIG_FILE, set AWS_SDK_LOAD_CONFIG=1', errno: 'ENETUNREACH', code: 'CredentialsError', syscall: 'connect', address: '127.0.0.1', port: 80, time: 2023-05-07T10:45:25.835Z, originalError: { message: 'Could not load credentials from any providers', errno: 'ENETUNREACH', code: 'CredentialsError', syscall: 'connect', address: '127.0.0.1', port: 80, time: 2023-05-07T10:45:25.835Z, originalError: [Object] }
Related: How to test methods that return data from AWS DynamoDB
You are mocking the
The solution is to move the db declaration to its own module, for example: db.js
db
declared in the test file - not thesaveUser
that is actually used.Then import it from the
saveUsermodule and test - this way we can mock the same
renewdb
instance thatsaveUser
is using.I was able to successfully run the test using the following code:
Test code:
User handler file:
package.json
OutputSeparate database connections in files
We can separate the database connection into a different file and import it into the handler implementation as well as the spec file.
db.js
yields()
FunctionThe stub should not return a
Promise
directly, but should be chained with the arguments that.yields()
and its callbacks will accept. We can change parameters to cover various branches of the code.Code
Useful links
https://www.youtube.com/watch?v=vXDbmrh0xDQ