How to mock DynamoDBDocumentClient constructor with Jest (AWS SDK V3) - javascript

I'm working with Jest to mock my AWS services, and more specifically DynamoDB and DynamoDBDocumentClient.
My code is currently similar to this :
import { DynamoDBClient } from "#aws-sdk/client-dynamodb"
import { DynamoDBDocumentClient } from "#aws-sdk/lib-dynamodb"
const ddbClient = new DynamoDBClient({})
const ddbDocumentClient = DynamoDBDocumentClient.from(ddbClient, config)
and the test spec looks like this:
jest.mock("#aws-sdk/client-dynamodb", () => ({
DynamoDBClient: jest.fn(() => ({
put: (params) => mockAwsResponse("DynamoDBClient", "GetCommand", params),
put: (params) => mockAwsResponse("DynamoDBClient", "PutCommand", params),
})),
}))
jest.mock("#aws-sdk/lib-dynamodb", () => ({
DynamoDBDocumentClient: jest.fn(() => ({
get: (params) => console.log("In DynamoDBClient get", params),
put: (params) => mockAwsResponse("DynamoDBDocumentClient", "PutCommand", params),
from: (params) => mockAwsResponse("DynamoDBDocumentClient", "from", params),
})),
}))
Unfortunately Jest returns me this error : TypeError: lib_dynamodb_1.DynamoDBDocumentClient.from is not a function and I believe it's because I'm incorrectly mocking DynamoDBDocumentClient, as its constructor is a static method.
Thanks in advance for your help!
EDIT: Just noticed that it used the AWS SDK v3 for JavaScript, if that helps.

In your example, you import your modules and run call your constructor right away (before entering any method in your script), this means that the object you are trying to mock needs to be available before entering any test methods.
Try to place your mocks before (and outside of) your tests something like this:
import { DynamoDBClient } from "#aws-sdk/client-dynamodb"
import { DynamoDBDocumentClient } from "#aws-sdk/lib-dynamodb"
/* whatever constants you need for your mocks */
jest.mock('#aws-sdk/client-dynamodb', () => {
return {
DynamoDBClient: jest.fn().mockImplementation(() => {
return {};
}),
};
});
jest.mock('#aws-sdk/lib-dynamodb', () => {
return {
DynamoDBDocumentClient: {
from: jest.fn().mockImplementation(() => {
return {
send: jest.fn().mockImplementation((command) => {
let res = 'something';
if(command.name == 'GetCommand'){
res = 'something else (a constant from earlier...)';
}
/* return whatever needs to be returned... */
return Promise.resolve(res);
}),
};
}),
},
/* Return your other docClient methods here too... */
GetCommand: jest.fn().mockImplementation(() => {
return { name: 'GetCommand' };
}),
};
});
// then you can implement your tests
describe('Endpoint something', () => {
it('Should pass or something...', async () => {
/* your test here... */
});
});
Now your functions should be available when they are needed.

Related

how to unit test if handler function is called when using middy

im using an http request function as the handler function in middy and then use the ssm middleware to fetch some ssm parameters before initiating the http request.
like this:
const makeThirdPartyServiceRequest = middy(async ({ params }) => {
logger.info(`SENDING Request to ${endpoint} API`)
const url = `https://someurltoathirdpartyservice`
const options = {
method: 'POST',
body: params
}
return helpers.makeRequest(url, options)
})
makeThirdPartyServiceRequest.use(ssm(......))
However in my jest unit test Im trying to mock makeThirdPartyServiceRequest and explicitly say it should resolve to a value:
jest.mock('../src/thirdPartyService', () => ({
__esModule: true,
default: {
...(jest.requireActual('../src/thirdPartyService') as { default: {} }).default,
makeThirdPartyServiceRequest: jest.fn()
}
}))
export {}
import thirdPartyService from '../src/thirdPartyService'
And then in the test i say:
describe('makeThirdPartyServiceRequest()', () => {
it('should makeThirdPartyServiceRequest', async () => {
// Given
// })
const mockedThirdPartyServiceRequest = mocked(thirdPartyService.makeThirdPartyServiceRequest).mockResolvedValue({})
// When
const result = await thirdPartyService.makeThirdPartyServiceRequest(something)
// Then
expect(mockedThirdPartyServiceRequest).toHaveBeenCalledTimes(1)
expect(mockedThirdPartyServiceRequest.mock.calls[0][0].params.toString()).toBe(expectedParams)
})
})
However for some reason the middy middleware is still being invoked, which i clearly dont want and i have tried to mock away... what am i doing wrong?
You need to mock middy instead, to make it becomes a useless function. That function recipe a function as a parameter and return that parameter.
import thirdPartyService from '../src/thirdPartyService'
jest.mock('#middy/core', () => {
return (handler) => {
return {
use: jest.fn().mockReturnValue(handler), // ...use(ssm()) will return handler function
}
}
})
describe('thirdPartyService()', () => {
beforeEach(() => {
jest.spyOn(helpers, 'makeRequest') // spy on helpers unit
})
describe('makeThirdPartyServiceRequest', () => {
it('should make a request with correct parameters', async () => {
// Given
const url = `https://someurltoathirdpartyservice`
const params = 'any params'
const apiResponse = 'any response'
mocked(helpers.makeRequest).mockResolvedValue(apiResponse)
// When
const actual = await thirdPartyService.makeThirdPartyServiceRequest(params)
// Then
expect(actual).toBe(apiResponse)
expect(helpers.makeRequest).toHaveBeenCalledWith(
url,
{
method: 'POST',
body: params
}
)
})
})
})
hoangdv answer is also valid, but i will answer as well how i continued.
if you completely want to mock middy you mock like following:
jest.mock('#middy/core', () => {
return (handler) => {
return {
use: jest.fn().mockImplementation(() => {
// ...use(ssm()) will return handler function
return {
before: jest.fn().mockReturnValue(handler)
}
})
}
}
})
However if you dont want to completely mock middy, you can instead mock the async getInternal function from middy/util called in before like this:
jest.doMock('#middy/util', () => ({
...(jest.requireActual('#middy/util') as {}),
getInternal: jest.fn()
}))
import { getInternal } from '#middy/util'
and then in the test
describe('thirdPartyService()', () => {
beforeEach(() => {
jest.spyOn(helpers, 'makeRequest') // spy on helpers unit
})
describe('makeThirdPartyServiceRequest', () => {
it('should make a request with correct parameters', async () => {
// Given
const url = `https://someurltoathirdpartyservice`
const params = 'any params'
const apiResponse = 'any response'
mocked(getInternal).mockResolvedValue({
twilioSecrets: { accountSid: 'someSID', serviceId:
'someServiceID', token: 'someToken' }
})
mocked(helpers.makeRequest).mockResolvedValue(apiResponse)
// When
const actual = await thirdPartyService.makeThirdPartyServiceRequest(params)
// Then
expect(actual).toBe(apiResponse)
expect(helpers.makeRequest).toHaveBeenCalledWith(
url,
{
method: 'POST',
body: params
}
)
})
})
})
this will mock the async part of middy.

Jest: How to properly test a Javascript service using MongoDB

I'm a total beginner with Jest.
I've got a UserService using Dependency Injection.
public async getAll() {
const userRecords = await this.userModel.find().select('name').catch((e) => {
throw new HttpException(500, 'Error while fetching users.', e)
});
return <[IUser]>userRecords;
}
I would like to test this feature. Here are the tests I could run:
Calling the route, and checking if the resulting JSON is OK
Ggetting DB content, and checking if it is as expected
Just test the getAll function
I think 1 and 2 are obvious, and cover different kind of things. 1 covers the request part, 2 covers the DB part. But what about number 3? How to "just test" the getAll function?
I've tried this:
const userModel = {
find: (user) => {
return [
{ id: 'user1' },
{ id: 'user2' }
]
},
};
const userService = new UserService(userModel);
const userRecords = await userService.getAll();
expect(argumentRecord).toBeDefined();
But obviously it's failing because select is undefined.
Should I also mock select()? Should I organize my code differently?
If were to write this test I would mock the functions using jest.fn(implementation) so that expectations can be enforced on the function calls.
const userQuery = {
select: jest.fn(() => Promise.resolve([]))
};
const userModel = {
find: jest.fn(() => userQuery)
};
const userService = new UserService(userModel);
const userRecords = await userService.getAll();
expect(userRecords).toEqual([]);
expect(userModel.find).toHaveBeenCalled();
expect(userQuery.select).toHaveBeenCalledWith('name');
Performing expectations on the function calls may sound like overkill, but it explicitly verifies that the mock is actually being used by getAll.
I would also structure the tests in such a way that I can test the various code paths without re-implementing the entire mock.
describe('getAll()', () => {
let userQuery, userModel, userService;
beforeEach(() => {
userQuery = {
select: jest.fn(() => Promise.resolve([]))
};
userModel = {
find: jest.fn(() => userQuery)
};
userService = new UserService(userModel);
});
afterEach(() => {
expect(userModel.find).toHaveBeenCalled();
expect(userQuery.select).toHaveBeenCalledWith('name');
});
it('should get the user names', async () => {
const users = [{
name: 'john'
}, {
name: 'jane'
}];
userQuery.select.mockImplementation(() => Promise.resolve(users));
await expect(userService.getAll()).resolves.toBe(users);
});
it('should handle errors', async () => {
const error = new Error('Fake model error');
userQuery.select.mockImplementation(() => Promise.reject(error));
await expect(userService.getAll()).rejects.toMatch({
status: 500,
message: 'Error while fetching users.',
cause: error
});
});
});
This code is untested, so it may not work correctly, but hopefully it outlines the idea sufficiently.
While this is not directly related to your question I would avoid mixing async/await with traditional promise handling.
public async getAll() {
try {
return <[IUser]> await this.userModel.find().select('name');
} catch (e) {
throw new HttpException(500, 'Error while fetching users.', e)
}
}
Yes, you should mock select. And not only that, but everything that is used inside the function and test if they are executed properly. I would do this:
class SomeClass {
public async getAll() {
const userRecords = await this.userModel.find().select('name').catch(this.errorHandler);
return <[IUser]>userRecords;
}
public errorHandler(e) {
throw new HttpException(500, 'Error while fetching users.', e);
}
}
// this is just an example, it should be the same type as your expected returned output
const whatever = Math.random();
const fakeCatch = jest.fn(() => whatever);
const fakeSelect = jest.fn(() => {
return {
catch: fakeCatch
}
});
const fakeFind = jest.fn(() => {
return {
select: fakeSelect
};
});
const fakeUserModel = {
find: fakeFind,
}
const userService = new UserService(fakeUserModel);
const userRecords = await userService.getAll();
// should return the correct result
expect(userRecords).toEqual(whatever);
// should execute find
expect(fakeFind).toHaveBeenCalledTimes(1);
// should execute select with 'name' parameter
expect(fakeSelect).toHaveBeenCalledTimes(1);
expect(fakeSelect).toHaveBeenCalledWith('name');
// should execute catch with this.errorHandler
expect(fakeCatch).toHaveBeenCalledWith(userService.errorHandler);

How do I mock this method chain in Jest?

zoomOut(callback) {
// Zooms out the current screen
this.view.current.zoomOut(300).done(() => {
(hasCallback(callback)) && callback();
});
}
I'm trying to test the function above but I keep getting the following error:
TypeError: this.view.current.zoomOut(...).done is not a function
How can I mock this method chain in Jest?
Thanks to BudgieInWA, I was able to solve this problem by returning done.
For those who are testing a React component with Enzyme, here's how you can do it:
it('should call callback', () => {
const wrapper = shallow(<Zoom {...minProps}/>);
const instance = wrapper.instance();
const callback = jest.fn();
instance.view = {
current: {
zoomOut: jest.fn(() => {
return {
done: jest.fn((callback) => {
callback();
})
};
})
}
};
expect(callback).toHaveBeenCalledTimes(0);
instance.zoomOut(callback);
expect(callback).toHaveBeenCalledTimes(1);
});
You could try this:
const mockZoomOut = jest.fn(() => ({ done(cb) { cb(); } }));
const mockThis = {
view: {
current: {
zoomOut: mockZoomOut,
},
},
};
test('it does', () => {
const cb = jest.fn();
zoomOut.apply(mockThis, [cb]);
expect(mockZoomOut).toHaveBeenCalledTimes(1);
expect(cb).toHaveBeenCalledTimes(1);
});
See Jest Mock Functions and fn.apply.
If you are testing the behaviour of the class as a whole, then you could set up the instance that you are testing to have this.view.current.zoomOut be mockZoomOut somehow.

Mocking up static methods in jest

I am having trouble mocking up a static method in jest. Immagine you have a class A with a static method:
export default class A {
f() {
return 'a.f()'
}
static staticF () {
return 'A.staticF()'
}
}
And a class B that imports A
import A from './a'
export default class B {
g() {
const a = new A()
return a.f()
}
gCallsStaticF() {
return A.staticF()
}
}
Now you want to mock up A. It is easy to mock up f():
import A from '../src/a'
import B from '../src/b'
jest.mock('../src/a', () => {
return jest.fn().mockImplementation(() => {
return { f: () => { return 'mockedA.f()'} }
})
})
describe('Wallet', () => {
it('should work', () => {
const b = new B()
const result = b.g()
console.log(result) // prints 'mockedA.f()'
})
})
However, I could not find any documentation on how to mock up A.staticF. Is this possible?
You can just assign the mock to the static method
import A from '../src/a'
import B from '../src/b'
jest.mock('../src/a')
describe('Wallet', () => {
it('should work', () => {
const mockStaticF = jest.fn().mockReturnValue('worked')
A.staticF = mockStaticF
const b = new B()
const result = b.gCallsStaticF()
expect(result).toEqual('worked')
})
})
Hope this will help you
// code to mock
export class AnalyticsUtil {
static trackEvent(name) {
console.log(name)
}
}
// mock
jest.mock('../src/AnalyticsUtil', () => ({
AnalyticsUtil: {
trackEvent: jest.fn()
}
}))
// code to mock
export default class Manager {
private static obj: Manager
static shared() {
if (Manager.obj == null) {
Manager.obj = new Manager()
}
return Manager.obj
}
nonStaticFunc() {
}
}
// mock
jest.mock('../src/Manager', () => ({
shared: jest.fn().mockReturnValue({
nonStaticFunc: jest.fn()
})
}))
// usage in code
someFunc() {
RNDefaultPreference.set('key', 'value')
}
// mock RNDefaultPreference
jest.mock('react-native-default-preference', () => ({
set: jest.fn()
}))
// code to mock
export namespace NavigationActions {
export function navigate(
options: NavigationNavigateActionPayload
): NavigationNavigateAction;
}
// mock
jest.mock('react-navigation', () => ({
NavigationActions: {
navigate: jest.fn()
}
}))
I managed to mock it in a separate file in the __mocks__ folder using prototyping. So you would do:
function A() {}
A.prototype.f = function() {
return 'a.f()';
};
A.staticF = function() {
return 'A.staticF()';
};
export default A;
Here's an example with an ES6 import.
import { MyClass } from '../utils/my-class';
const myMethodSpy = jest.spyOn(MyClass, 'foo');
describe('Example', () => {
it('should work', () => {
MyClass.foo();
expect(myMethodSpy).toHaveBeenCalled();
});
});
We need to create a mock and give visibility for the mocked method to the test suite. Below full solution with comments.
let mockF; // here we make variable in the scope we have tests
jest.mock('path/to/StaticClass', () => {
mockF = jest.fn(() => Promise.resolve()); // here we assign it
return {staticMethodWeWantToMock: mockF}; // here we use it in our mocked class
});
// test
describe('Test description', () => {
it('here our class will work', () => {
ourTestedFunctionWhichUsesThisMethod();
expect(mockF).toHaveBeenCalled(); // here we should be ok
})
})
Using Object.assign on the mock constructor allows simultaneous mocking of the class and its static methods. Doing this allows you to achieve the same structure you get when creating a class with static members.
import A from '../src/a'
import B from '../src/b'
jest.mock('../src/a', () =>
Object.assign(
jest.fn(
// constructor
() => ({
// mock instance here
f: jest.fn()
})),
{
// mock static here
staticF: jest.fn(),
}
)
)
Jest Spies
I went with the route of using jest.spyOn.
Example
encryption.ts
export class Encryption {
static encrypt(str: string): string {
// ...
}
static decrypt(str: string): string {
// ...
}
}
property-encryption.spec.ts
import { Encryption } from './encryption'
import { PropertyEncryption } from './property-encryption'
describe('PropertyEncryption', () => {
beforeAll(() => {
jest
.spyOn(Encryption, 'encrypt')
.mockImplementation(() => 'SECRET')
jest
.spyOn(Encryption, 'decrypt')
.mockImplementation(() => 'No longer a secret')
})
it("encrypts object values and retains the keys", () => {
const encrypted = PropertyEncryption.encrypt({ hello: 'world' });
expect(encrypted).toEqual({ hello: 'SECRET' });
});
it("decrypts object values", () => {
const decrypted = PropertyEncryption.decrypt({ hello: "SECRET" });
expect(decrypted).toEqual({ hello: 'No longer a secret' });
});
})

Injecting a stubbed dependency into an es6 class with Sinon

I have the following modules:
const rp = require('request-promise');
// dummies.repository.js
exports.getDummiesData = () => {
const options = {
url: 'https://api.github.com/users',
headers: {
'User-Agent': 'Request-Promise',
},
json: true
};
return rp(options)
.then(r => r)
.catch(err => { throw err; });
}
The above file is a dependency to a class that I would like to test. I inject it to the below class via its constructor like so:
// dummies.service.js
const Dummies = require('./dummies.model');
class DummiesService {
constructor(dummiesRepository) {
this.dummiesRepository = dummiesRepository;
}
getDummies() {
return new Promise((resolve, reject) => {
this.dummiesRepository.getDummiesData()
.then((response) => {
// business logic
const dummies = response.map(d => new Dummies(d));
resolve(dummies);
})
.catch((response) => {
reject(response);
});
});
}
}
module.exports = DummiesService;
In the above, I want to test the method getDummies with sinon. My approach so far has been to stub out the dummiesRepository and inject that to my dummies service during instantiation, but I am getting an error. Here is how my test looks:
const DummiesService = require('../dummies.service');
const dummiesRepository = require('../dummies.repository');
let dummiesRepositoryStub;
afterEach(() => {
if(dummiesRepositoryStub){
dummiesRepositoryStub.restore();
}
});
describe('unit: dummies.service - when getting dummies data', () => {
it('it should resolve and return dummies data', () => {
const dummiesResponse = [
{ id: 1, login: 'dummy1', additionalData: 'data' },
{ id: 2, login: 'dummy2', additionalData: 'data' },
{ id: 3, login: 'dummy3', additionalData: 'data' },
];
dummiesRepositoryStub = sinon
.stub(dummiesRepository, 'getDummiesData')
.resolves(dummiesResponse);
dummiesService = new DummiesService(dummiesRepositoryStub);
// act + assert
return dummiesService.getDummies()
.then((response) => {
chai.expect(dummiesRepositoryStub.called).to.equal(true);
chai.expect(response).to.deep.equal(dummiesTransformedModel);
});
});
This test fails with error TypeError: this.dummiesRepository.getDummiesData is not a function. I am not sure why this is not working, I injected a stub implementation of dummiesRepository to the DummiesService class. Please assist.
I could not find a way to do constructor injection in the above code, but I found a work around to allow sinon to stub my method.
I had to remove dummyRepository from being a constructor dependency to just using require to include it. If the code looks as follows
const Dummies = require('./dummies.model');
const dummiesRepository = require('./dummies.repository');
class DummiesService {
constructor() {
this.dummiesRepository = dummiesRepository;
}
getDummies() {
return new Promise((resolve, reject) => {
this.dummiesRepository.getDummiesData()
.then((response) => {
// business logic
const dummies = response.map(d => new Dummies(d));
resolve(dummies);
})
.catch((response) => {
reject(response);
});
});
}
}
module.exports = DummiesService;
and the test is written the same as above, but instantiating DummyService without the dummyRepositoryStub as a parameter, everything works fine.
This works, but I would still like to know if there is a way to inject the dependency via the constructor, because this approach requires me to refactor a lot of my code.

Categories

Resources