Mocking dynamodb scan using jest - node.js

For the source code(dynamodb.js):
const AWS = require("aws-sdk");
const Promise = require("bluebird");
const client = new AWS.DynamoDB.DocumentClient();
module.exports.db = (method, params) => {
console.log("access dynamodb ");
return Promise.fromCallback(cb => client[method](params, cb));
};
Using the test that looks like this(dont want't to mock Promise.fromCallback):
describe("test", () => {
const realAWS = require("aws-sdk");
let fakePromise;
let fakeDynamo;
let dbClient;
beforeAll(function() {
fakePromise = jest.fn();
fakeDynamo = {
get: (params, cb) => {
fakePromise(params, cb);
}
};
realAWS.DynamoDB.DocumentClient = jest.fn(() => fakeDynamo);
dbClient = require("../dynamodb");
});
test.only("Test successed", done => {
let result = dbClient.db("get", null);
console.log("access dynamodb ");
expect(fakePromise).toHaveBeenCalled();
});
});
but when tun the test ,the error was heppend:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
if anyone can help me? Thanks!

You need to mock the implementation of the client.get method as a node callback style. So that Promise.fromCallback method will covert callback style to a promise.
Besides, you need to trigger the callback in your test case. That's why you got a timeout error.
E.g.
dynamodb.js:
const AWS = require('aws-sdk');
const Promise = require('bluebird');
const client = new AWS.DynamoDB.DocumentClient();
module.exports.db = (method, params) => {
console.log('access dynamodb');
return Promise.fromCallback((cb) => client[method](params, cb));
};
dynamodb.test.js:
const { db } = require('./dynamodb');
const AWS = require('aws-sdk');
jest.mock('aws-sdk', () => {
const mClient = { get: jest.fn() };
const mDynamoDB = {
DocumentClient: jest.fn(() => mClient),
};
return { DynamoDB: mDynamoDB };
});
const mockClient = new AWS.DynamoDB.DocumentClient();
describe('54360588', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should pass', async () => {
mockClient.get.mockImplementationOnce((err, callback) => {
callback(null, 'fake data');
});
const actual = await db('get', null);
expect(actual).toBe('fake data');
expect(mockClient.get).toBeCalledWith(null, expect.any(Function));
});
});
unit test result with coverage report:
PASS src/stackoverflow/54360588/dynamodb.test.js (10.74s)
54360588
✓ should pass (28ms)
console.log src/stackoverflow/54360588/dynamodb.js:177
access dynamodb
-------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
dynamodb.js | 100 | 100 | 100 | 100 | |
-------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.636s

Related

Mocked Jest callback method not called

I'm testing the NodeJS function/module below using jestjs
const fs = require("fs");
const path = require("path");
function read(filename, callback) {
fs.readFile(path.join(__dirname, filename), { encoding: "utf-8" }, callback);
}
module.exports = {
read,
};
The first test is:
test("callback data returned is correct", (done) => {
function callback(err, data) {
try {
expect(data).toBe("1");
done();
} catch (err) {
done(err);
}
}
read("./test", callback);
});
and is succesful (I have an actual real test file with the contents being 1.
The second one is a bit trickier. Since I'm providing a mockCallback function to read, why isn't the callback called?
const { read } = require("./callback");
describe("callback testing", () => {
test("callback is called", (done) => {
const mockCallback = jest.fn((err, data) => data);
read("./test", mockCallback);
expect(mockCallback).toHaveBeenCalled();
done();
});
});
The test fails and while inspecting the mock function it seems it wasn't called.
I also tried adding the following in order to mock fs.readFile, without much success:
const mock = require("mock-fs");
const fs = require("fs");
jest.mock("fs");
beforeEach(() => {
mock({
test: "1",
});
});
I'd like a solution that mocks the least of the dependant methods and an explanation as to why isn't the callback run.
You should mock fs.readFile method and trigger the callback in your test case.
E.g.
callback.js:
const fs = require('fs');
const path = require('path');
function read(filename, callback) {
fs.readFile(path.join(__dirname, filename), { encoding: 'utf-8' }, callback);
}
module.exports = {
read,
};
callback.test.js:
const { read } = require('./callback');
const fs = require('fs');
describe('callback testing', () => {
test('callback is called', () => {
const mockCallback = jest.fn();
const mockData = 'mock file data';
const mockReadFile = jest.spyOn(fs, 'readFile').mockImplementationOnce((filename, options, callback) => {
callback(null, mockData);
});
read('./test', mockCallback);
expect(mockCallback).toHaveBeenCalled();
expect(mockReadFile).toBeCalledWith(expect.any(String), { encoding: 'utf-8' }, mockCallback);
mockReadFile.mockRestore();
});
});
unit test result:
PASS examples/65361608/callback.test.js
callback testing
✓ callback is called (3 ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
callback.js | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.992 s, estimated 5 s

How to mockup a constant defined in another file using sinon and rewire?

I'm a beginner with JS tests and I'm having issues when I try to mockup the value of a constant in the file I need to test.
I have the following file
// index.js
const { MultiAccounts } = require('../../some.js')
const MultiAccountInstance = new MultiAccounts();
...
const syncEvents = () => Promise.try(() => {
// ...
return MultiAccountInstance.all()
.then((accounts) => // ...); // ==> it throws the exception here Cannot read property 'then' of undefined
});
module.exports = syncEvents;
So, I will like to mockup the MultiAccountInstance constant.
I had been trying using Simon and rewire, but with the following script I'm having it throws the exception here Cannot read property 'then' of undefined exception in the script above.
//index.test.js
const rewire = require('rewire');
const indexRewired = rewire('.../../index/js');
describe('testing sync events', () => {
let fakeMultiAccountInstance, MultiAccountInstanceReverter;
let accounts;
beforeEach(() => {
accounts = [{id: 1}, {id: 2}];
fakeMultiAccountInstance = {};
fakeMultiAccountInstance.all = () => Promise.resolve(accounts);
MultiAccountInstanceReverter = indexRewired.__set__('MultiAccountInstance', fakeMultiAccountInstance);
});
afterEach(() => {
MultiAccountInstanceReverter();
});
it('testing', ()=> {
const spy = sinon.stub(fakeMultiAccountInstance, 'all');
return indexRewired().then((resp) => {
spy.restore();
expect(spy).to.have.been.calledWith({someParams: true});
});
})
});
How can I achieve this?. I also tried using stubs, but I'm having the error that the MultiAccountInstance.all is not a function
it's something like this
//index.test.js
const rewire = require('rewire');
const indexRewired = rewire('.../../index/js');
describe('testing sync events', () => {
let stubMultiAccountInstance, MultiAccountInstanceReverter;
let accounts;
beforeEach(() => {
accounts = [{id: 1}, {id: 2}];
stubMultiAccountInstance= sinon.stub().returns({
all: () => Promise.resolve(accounts), // also tried with sinon.stub().resolves(accounts)
});
MultiAccountInstanceReverter = indexRewired.__set__('MultiAccountInstance', stubMultiAccountInstance);
});
afterEach(() => {
stubMultiAccountInstance.reset();
MultiAccountInstanceReverter();
});
it('testing', ()=> {
return indexRewired().then((resp) => {
expect(stubMultiAccountInstance).to.have.been.calledWith({someParams: true});
});
})
});
Do you know what am I doing wrong?
Here is the unit test solution:
index.js:
const { MultiAccounts } = require('./some.js');
const Promise = require('bluebird');
let MultiAccountInstance = new MultiAccounts();
const syncEvents = () =>
Promise.try(() => {
return MultiAccountInstance.all().then((accounts) => console.log(accounts));
});
module.exports = syncEvents;
some.js:
function MultiAccounts() {
async function all() {}
return {
all,
};
}
module.exports = { MultiAccounts };
index.test.js:
const sinon = require('sinon');
const rewire = require('rewire');
const Promise = require('bluebird');
describe('61659908', () => {
afterEach(() => {
sinon.restore();
});
it('should pass', async () => {
const promiseTrySpy = sinon.spy(Promise, 'try');
const logSpy = sinon.spy(console, 'log');
const indexRewired = rewire('./');
const accounts = [{ id: 1 }, { id: 2 }];
const fakeMultiAccountInstance = {
all: sinon.stub().resolves(accounts),
};
indexRewired.__set__('MultiAccountInstance', fakeMultiAccountInstance);
await indexRewired();
sinon.assert.calledOnce(fakeMultiAccountInstance.all);
sinon.assert.calledWith(logSpy, [{ id: 1 }, { id: 2 }]);
sinon.assert.calledOnce(promiseTrySpy);
});
});
unit test results with coverage report:
61659908
[ { id: 1 }, { id: 2 } ]
✓ should pass (54ms)
1 passing (62ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 80 | 100 |
index.js | 100 | 100 | 100 | 100 |
some.js | 100 | 100 | 50 | 100 |
----------|---------|----------|---------|---------|-------------------

sinon stub for Lambda using promises

I just started using sinon, and I had some initial success stubbing out DynamoDB calls:
sandbox = sinon.createSandbox()
update_stub = sandbox.stub(AWS.DynamoDB.DocumentClient.prototype, 'update').returns({
promise: () => Promise.resolve(update_meeting_result)
})
This works great.
But I also need to stub Lambda, and the same approach isn't working:
lambda_stub = sandbox.stub(AWS.Lambda.prototype, 'invoke').returns({
promise: () => Promise.resolve({lambda_invoke_result}) //
})
With this, I get the error: Cannot stub non-existent property invoke.
example implementation:
const AWS = require("aws-sdk")
AWS.config.update({region: 'us-west-2'})
const dynamodb = new AWS.DynamoDB.DocumentClient()
const lambda = new AWS.Lambda()
// lambda function handler
exports.handler = async (event) => {
let result = await dynamodb.get({/* some get config */}).promise()
// do stuff ...
// kick off next lambda
await lambda.invoke({/* lambda config */}).promise()
return {"status": "ok"} // or something
}
Here is the unit test solution:
index.js:
const AWS = require('aws-sdk');
AWS.config.update({ region: 'us-west-2' });
const dynamodb = new AWS.DynamoDB.DocumentClient();
const lambda = new AWS.Lambda();
exports.handler = async (event) => {
let result = await dynamodb.get({}).promise();
await lambda.invoke({}).promise();
return { status: 'ok' };
};
index.test.js:
const sinon = require('sinon');
const AWS = require('aws-sdk');
describe('61516053', () => {
afterEach(() => {
sinon.restore();
});
it('should pass', async () => {
const mLambda = { invoke: sinon.stub().returnsThis(), promise: sinon.stub() };
sinon.stub(AWS, 'Lambda').callsFake(() => mLambda);
const mDocumentClient = { get: sinon.stub().returnsThis(), promise: sinon.stub() };
sinon.stub(AWS.DynamoDB, 'DocumentClient').callsFake(() => mDocumentClient);
sinon.stub(AWS.config, 'update');
const { handler } = require('./');
await handler();
sinon.assert.calledWith(AWS.config.update, { region: 'us-west-2' });
sinon.assert.calledOnce(AWS.DynamoDB.DocumentClient);
sinon.assert.calledOnce(AWS.Lambda);
sinon.assert.calledWith(mLambda.invoke, {});
sinon.assert.calledOnce(mLambda.promise);
sinon.assert.calledWith(mDocumentClient.get, {});
sinon.assert.calledOnce(mDocumentClient.promise);
});
});
unit test results with 100% coverage:
61516053
✓ should pass (907ms)
1 passing (915ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------

How to test SSM getParameter method in jasmine?

How do I test something like this?
const ssmParameterData = await ssm.getParameter(params, async (error, data) => {
if (error) throw error;
return data;
}).promise();
I have tried just mocking the method
spyOn(ssm, 'getParameter').and.returnValue(ssmParams);
and I get error like
TypeError: Cannot read property 'promise' of undefined
Here is the unit test solution:
index.js:
const AWS = require('aws-sdk');
const ssm = new AWS.SSM();
async function main(params) {
const ssmParameterData = await ssm
.getParameter(params, (error, data) => {
if (error) throw error;
return data;
})
.promise();
return ssmParameterData;
}
module.exports = { ssm, main };
index.test.js:
const { ssm, main } = require('./');
describe('60138152', () => {
it('should pass', async () => {
const data = 'fake data';
const getParameterRequestStub = { promise: jasmine.createSpy('promise') };
const getParameterStub = spyOn(ssm, 'getParameter').and.callFake((params, callback) => {
callback(null, data);
getParameterRequestStub.promise.and.resolveTo(data);
return getParameterRequestStub;
});
await expectAsync(main('test params')).toBeResolvedTo('fake data');
expect(getParameterStub).toHaveBeenCalledWith('test params', jasmine.any(Function));
expect(getParameterRequestStub.promise).toHaveBeenCalledTimes(1);
});
it('should throw error', async () => {
const mError = new Error('network');
const getParameterRequestStub = { promise: jasmine.createSpy('promise') };
const getParameterStub = spyOn(ssm, 'getParameter').and.callFake((params, callback) => {
callback(mError);
getParameterRequestStub.promise.and.rejectWith(mError);
return getParameterRequestStub;
});
await expectAsync(main('test params')).toBeRejectedWithError('network');
expect(getParameterStub).toHaveBeenCalledWith('test params', jasmine.any(Function));
});
});
unit test results with 100% coverage:
Executing 2 defined specs...
Running in random order... (seed: 87758)
Test Suites & Specs:
(node:93291) ExperimentalWarning: The fs.promises API is experimental
1. 60138152
✔ should pass (10ms)
✔ should throw error (1ms)
>> Done!
Summary:
👊 Passed
Suites: 1 of 1
Specs: 2 of 2
Expects: 5 (0 failures)
Finished in 0.034 seconds
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
source code: https://github.com/mrdulin/jasmine-examples/tree/master/src/stackoverflow/60138152

How can I mock a single method from a mocked class in jest?

I am using node 12 and jest for unit testing. I have a code to open connection via websocket.
const ws = require('ws');
this.ws = new WebSocket(url);
this.ws.on('open', () => {
// How to test this callback?
...
resolve();
});
this.ws.on('error', (err) => {
// How to test this callback?
...
reject(err)
});
In my test case, I have mocked ws module via jtest:
const WebSocket = require('ws');
jest.mock('ws')
test('quote server listener should be able to connect to quote server', () => {
const server = new QuoteServerListener(null, 'http://mock.com');
server.connect();
const mockWSInstance = WebSocket.mock.instances[0];
expect(mockWSInstance.on).toHaveBeenCalledTimes(1);
});
The above test case works fine. But I don't know how to trigger a call to the callback function on ws.on('open', () => .... I'd like to test the logic when the connection is open. How can I achieve this in the mock?
I have tried to emit a open event via mockWSInstance instance like mockWSInstance.emit('open', null) but it doesn't trigger the code. What should I do in this case?
Here is the unit test solution:
index.ts:
export class SomeClass {
ws;
run() {
const WebSocket = require("ws");
const url = "";
this.ws = new WebSocket(url);
return new Promise((resolve, reject) => {
this.ws.on("open", () => {
resolve();
});
this.ws.on("error", err => {
reject(err);
});
});
}
}
index.spec.ts:
import { SomeClass } from "./";
const WebSocket = require("ws");
jest.mock("ws", () => {
const mWebSocket = {
on: jest.fn()
};
return jest.fn(() => mWebSocket);
});
describe("SomeClass", () => {
let instance;
let ws;
beforeEach(() => {
ws = new WebSocket();
instance = new SomeClass();
});
afterAll(() => {
jest.resetAllMocks();
});
it("should pass", async () => {
const eventHandler = {};
ws.on.mockImplementation((event, handler) => {
eventHandler[event] = handler;
});
const pending = instance.run();
eventHandler["open"]();
const actual = await pending;
expect(actual).toBeUndefined();
expect(ws.on).toBeCalledWith("open", eventHandler["open"]);
});
it("should fail", async () => {
const eventHandler = {};
ws.on.mockImplementation((event, handler) => {
eventHandler[event] = handler;
});
const pending = instance.run();
const mError = new Error("connection error");
eventHandler["error"](mError);
await expect(pending).rejects.toThrowError(mError);
expect(ws.on).toBeCalledWith("error", eventHandler["error"]);
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/59084313/index.spec.ts
SomeClass
✓ should pass (4ms)
✓ should fail (3ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.781s, estimated 8s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59084313

Resources