How to mock AWS Cognito CognitoIdentityServiceProvider with Jest? - node.js

I'm trying to write unit test where I need to mock response of method from cognito service -
CognitoIdentityServiceProvider
I have the following working code calling the adminInitiateAuth operation
import * from AWS from 'aws-sdk'
const cognito = new AWS.CognitoIdentityServiceProvider();
const response = await cognito.adminInitiateAuth(expectedParams).promise();
// main functionality I want to test
and I want to have a spec where I try to mock this service as prerequisites
const mockResponse = {
AuthenticationResult: {
AccessToken: 'expected-token'
}
}
jest.mock('aws-sdk', () => {
return {
CognitoIdentityServiceProvider: {
adminInitiateAuth: () => {
return mockResponse;
}
}
}
});
this returns me an error
AWS.CognitoIdentityServiceProvider is not a constructor
How this can not be a constructor?
Do you have any ideas how to mock it?

I figured it out. It may be useful to someone
jest.mock('aws-sdk', () => {
return {
CognitoIdentityServiceProvider: class {
adminInitiateAuth() {
return this;
}
promise() {
return Promise.resolve(mockResponse);
}
}
}
});

Here's another way to do it if we want to use spyOn to mock a specific function:
const cognotoIdentityServiceProvider = Object.getPrototypeOf(new AWS.CognitoIdentityServiceProvider());
const stub = jest.spyOn(cognotoIdentityServiceProvider, 'adminDeleteUser').mockReturnValue({
promise: () => Promise.resolve('bob johnson'),
});

Hope this will help
jest.mock("aws-sdk", () => {
const cognito = { listUsers: jest.fn() };
return {
CognitoIdentityServiceProvider: jest.fn(() => cognito),
config: {
update: jest.fn(),
},
};
});
const mCognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider();
mCognitoIdentityServiceProvider.listUsers.mockImplementationOnce(() => {
return {
promise() {
return Promise.resolve('your mock data');
},
};
});

Using the AWS SDK for JavaScript v3
jest.mock('#aws-sdk/client-cognito-identity-provider', () => {
return {
CognitoIdentityProviderClient: class {
send() {
return mockCognitoResponseAddGroup
}
promise() {
return Promise.resolve({})
}
},
CreateGroupCommand: class {},
}
})

Related

How to test throwing error from module root (not method)

We have module within there is some initial logic which test that some value was configured, if not, it throws error.. then also the module provides methods. I want to describe this in specification (test), using Jest framework and test the feature. Here is simplified reproduced example:
// dependency.service.ts
export const something = {
method() {
return "methodValue";
}
};
export default function somethingElse() {
return "somethingElseValue";
}
// index.ts
import somethingElse, { something } from "./dependency.service";
const value1 = somethingElse();
const value2 = something.method();
console.log("ROOT somethingElse", value1);
console.log("ROOT something.method", value2);
// initialisation of module fails
if(value1 === 'throw_error' || value2 === 'throw_error') {
throw 'Some error';
}
export function smElse() {
const value = somethingElse();
console.log("somethingElse", value);
return value;
}
export function smMethod() {
const value = something.method();
console.log("somethingElse", value);
return value;
}
// index.spec.ts
import { smElse, smMethod } from './index';
jest.mock('./dependency.service', () => ({
__esModule: true,
default: jest.fn(() => 'MOCKED_somethingElseValue'),
something: {
method: jest.fn(() => 'MOCKED_methodValue'),
},
}));
describe('index', () => {
// some tests for happy paths
it('smElse returns mocked value', () => {
expect(smElse()).toMatchInlineSnapshot(`"MOCKED_somethingElseValue"`);
});
it('smMethod returns mocked value', () => {
expect(smMethod()).toMatchInlineSnapshot(`"MOCKED_methodValue"`);
});
it('smElse returns per test mocked value', () => {
const somethingElseMocked = require('./dependency.service').default;
somethingElseMocked.mockReturnValueOnce('ANOTHER_MOCKED_somethingElseValue');
expect(smElse()).toMatchInlineSnapshot(`"ANOTHER_MOCKED_somethingElseValue"`);
});
it('smMethod returns per test mocked value', () => {
const something = require('./dependency.service').something;
something.method.mockReturnValueOnce('ANOTHER_MOCKED_methodValue');
expect(smMethod()).toMatchInlineSnapshot(`"ANOTHER_MOCKED_methodValue"`);
});
// this is testing the throwing error in module root
it('throws error when somethingElse returns specific message', () => {
expect.assertions(1);
jest.isolateModules(() => {
const somethingElseMocked = require('./dependency.service').default;
somethingElseMocked.mockReturnValueOnce('throw_error');
try {
require('./index');
} catch (error) {
expect(error).toBe("Some error");
}
});
});
it('throws error when something.method returns specific message', () => {
expect.assertions(1);
jest.isolateModules(() => {
const somethingMethodMocked = require('./dependency.service').something.method;
somethingMethodMocked.mockReturnValueOnce('throw_error');
try {
require('./index');
} catch (error) {
expect(error).toBe("Some error");
}
});
});
});
"Try catch in isolation" solution does not work with async code as isolateModules method does not support async functions yet, reference: https://github.com/facebook/jest/issues/10428. I need alternative solution which would support async code.
Whole reproduced example repo here: https://github.com/luckylooke/jestTestModuleRootThrow/tree/main
EDIT:
I found out that some asynchronicity is supported by isolateModules, at least my use case, once I used expect.assertions(1) following test works as expected:
it('throws error when token data are not valid', async () => {
expect.assertions(1);
jest.isolateModules(async () => {
require('crypto-package').someMethod.mockReturnValueOnce(Promise.resolve({
decoderMethod: () => Promise.resolve('{ broken data }'),
}));
const { getTokenData } = require("./decoder.service");
await expect(getTokenData('34534xxxxxxxxxxxxxxx12628')).rejects.toMatchInlineSnapshot(`"Invalid token data"`);
});
});

extracting mocked function for easier mocking of function behavior with jest

I'm using jest with nodejs and sequelize for my models. For my testing, I wanted to mock the returned value of findAll to cover test scenarios. Sorry if this is a very newbie question but I'm at dead-end on this one.
init-models.js
module.exports = function initModels(sequelize) {
//model relationship code here
...
...
//end of model relationship code
return {
records,
anotherModel,
alsoAnotherModel
};
};
repository.js
const sequelize = require('../sequelize');
const initModels = require('../model/init-models');
let {
records,
anotherModel,
alsoAnotherModel
} = initModels(sequelize);
const fetchRecords = async () => {
console.info('Fetching records...');
return await records.findAll({sequelize parameters here});
}
repository.test.js This will work but needs the flexibility to mock findAll() return value/or throw Error
const repository = require('../../../src/db/repository/repository');
const initModels = require('../../../src/db/model/init-models');
jest.mock('../../../src/db/model/init-models', () => {
return function() {
return {
records: {
findAll: jest.fn().mockImplementation(() => [1,2,3])
}
//the rest of the code for other models
}
}
});
describe('fetchRecords', () => {
beforeEach(()=> {
});
test('should return correct number of records', async () => {
const result = await repository.fetchRecords();
expect(result.size).toStrictEqual(3); //test passed
});
})
To allow mocking of results of findAll, I've tried extracting it so I can change the result per test scenario, but it was not working. What did I missed?
const mockRecordsFindAll = jest.fn();
jest.mock('../../../src/db/model/init-models', () => {
return function() {
return {
records: {
findAll: () => mockRecordsFindAll
}
//the rest of the code for other models
}
}
});
describe('fetchRecords', () => {
beforeEach(()=> {
mockRecordsFindAll.mockReset()
});
test('should return correct number of records', async () => {
mockRecordsFindAll.mockImplementation(() => [1,2,3]); //should expect length 3
const result = await repository.fetchRecords();
expect(result.size).toStrictEqual(3); //fails, findAll was not mocked
});
})
The issue is mockRecordsFindAll is being returned instead of executed.
As #Gid machined just returning mockRecordsFindAll causes initialization issues (due to hoisting).
The solution for this case is using the decorator pattern to allow mockRecordsFindAll to be initialized afterward.
const mockRecordsFindAll = jest.fn();
jest.mock('../../../src/db/model/init-models', () => {
return function() {
return {
records: {
findAll: function () {
return mockRecordsFindAll.call(this, arguments);
}
}
}
}
});
describe('fetchRecords', () => {
...
})

Jest Unit Test for Node Service having async methods

I'm getting below errors. Why I'm receiving response as undefined from the service?
Is there anything wrong I did for providing mock implementations?
Service:
export class SaveDataService{
async save() : Promise<any> {
try{
return this.someFunction()
} catch(ex){
throw new Error('some error occured')
}
}
async someFunction() : Promise<any>{
const response = {
"file" : "<htm><body>This is sample response</body></html>"
}
return Promise.resolve(response);
}
}
Test/Spec file:
import { SaveDataService } from "./save-data.service";
jest.mock('./save-data.service')
describe('tests for SaveDataService', () => {
it('when save method is called and success result is returned', async () => {
let mockSaveDataServiceSomeFunction = jest.fn().mockImplementation(() => {
return Promise.resolve('Success Result')
});
SaveDataService.prototype.someFunction = mockSaveDataServiceSomeFunction;
let spy = jest.spyOn(SaveDataService.prototype, 'someFunction');
let service = new SaveDataService();
let data = await service.save()
expect(data).toEqual('Success Result')
expect(spy).toHaveBeenCalled()
})
it('when save method is called and error is returned', async () => {
let mockSaveDataServiceSomeFunction = jest.fn().mockImplementation(() => {
throw new Error('ERROR')
});
SaveDataService.prototype.someFunction = mockSaveDataServiceSomeFunction;
let spy = jest.spyOn(SaveDataService.prototype, 'save');
let service = new SaveDataService();
service.save()
expect(spy).toThrowError('ERROR')
})
})
A mock replaces the dependency. You set expectations on calls to the dependent object, set the exact return values it should give you to perform the test you want, and/or what exceptions to throw so that you can test your exception handling code.
In this scenario, you are mocking save-data.service by calling jest.mock('./save-data.service'). So that your class may looks like this:
async save() : Promise<any> {
// do nothing or undefined
}
async someFunction() : Promise<any> {
// do nothing or undefined
}
So you must implement the body yourself to expect what exactly you want the method/function to do for you. You are mocking only the someFunction:
...
let mockSaveDataServiceSomeFunction = jest.fn().mockImplementation(() => {
return Promise.resolve('Success Result')
});
SaveDataService.prototype.someFunction = mockSaveDataServiceSomeFunction;
...
So when you call the save() method you still get nothing/undefined.
You are overwriting the whole behavior of the service that I think your test may not be useful. But you can fix your test this way:
import { SaveDataService } from "./save-data.service";
jest.mock('./save-data.service');
describe('tests for SaveDataService', () => {
beforeEach(() => {
SaveDataService.mockClear();
});
it('when save method is called and success result is returned', async () => {
const spy = jest
.spyOn(SaveDataService.prototype, 'save')
.mockImplementation(async () => Promise.resolve('Success Result'));
const service = new SaveDataService();
const data = await service.save();
expect(data).toEqual('Success Result');
expect(spy).toHaveBeenCalled();
})
it('when save method is called and error is returned', async () => {
const spy = jest
.spyOn(SaveDataService.prototype, 'save')
.mockImplementation(() => {
throw new Error('ERROR');
});
const service = new SaveDataService();
expect(service.save).toThrowError('ERROR');
expect(spy).toHaveBeenCalled();
});
});

sinon stub a firebase collection with chained methods for unit testing...(sinon, node/express, Jasmine)

In my nodejs code, I have a snippet:
await cfDB.collection('users').doc(userAccount.uid).set({
email: 'email#domain.com',
firstName: 'firstname',
lastName: 'Lastname,
});
How would I create a sinon mock of this for unit test with jasmine?
more complete context....
const { Firestore } = require('#google-cloud/firestore');
const cfDB = new Firestore();
// Create Firestore user document
try {
await cfDB.collection('users').doc('E7skP0IncSW7wkBxnrYFx6udzGH2').set({
email: 'email#domain.com',
firstName: 'Firstname',
lastName: 'Lastname',
});
} catch (error) {
res.json({ success: false, payload: { message: 'Firebase User write error. ' + error } });
return;
}
// Success
res.json({ success: true, payload: { message: 'User created.', userAccount: userAccount } });
}
Here is the solution...
const collectionStub = sinon.stub(firebase.cfDB, 'collection')
.get(() => {
return function() {
return {
doc: (path) => {
return {
set: () => [{user: 'mock-user-1'}, {user: 'mock-user-2'}]
}
}
}
}
});
In the documentation for Google Functions there is a
number of great examples of using sinon to stub Firebase Realtime Database and Firestore. Its the ones listed as offline examples.
I believe they should be able to work in any Node.js context. Also express.
You can find them here: https://firebase.google.com/docs/functions/unit-testing
I'm still working on it, but I've been able to get this far for now:
function stubTest(path: string, result: any){
sinon.stub(admin, 'firestore')
.get(() => {
return function() {
return stubCollection(path, result);
}
});
}
function stubDoc(path: string, result: any){
const parts = path.split('/');
const rest = parts.slice(1).join('/');
return {
doc: () => {
if(rest){
return stubCollection(rest, result);
}else {
return {
get: () => stubData(result)
}
}
}
}
}
function stubCollection(path: string, result: any){
const parts = path.split('/');
const rest = parts.slice(1).join('/');
return {
collection: () => {
if(rest){
return stubDoc(rest, result);
}else {
return {
get: () => stubData(result)
}
}
}
}
}
function stubData(result: any){
return {
data: () => result
}
}
and to use it you only need to call stubTest("restaurants/12333/orders/123111", {mockResponse: "someData"});
this will mock a call like this one
const order = (await firestore.collection('restaurants').doc('12333').collection('orders').doc('123111').get()).data();
Ofc there is a long way to go, adding more mock methods but at least I dont need to write stubs multiple times
Here is a complete example on how I use sinon to mock away Firestore.
In the function-under-test getAssets I pass firestore as its first argument. You can also choose just replace a global firestore variable using rewire or proxyquire or any other way, that would be out of scope for this answer.
In the test, there is an example AssetData[] that represents a collection. And the call to setupAssets turns this into a mocked firestore that would return this collection when queried.
The function setupAssets returns all the intermediate mocks it creates as well, these can be inspected for their call arguments and number of calls in your test.
Example test with mocked firestore:
it('gets existing assets', async () => {
const userId = 'user-id'
const assetName = 'asset-name'
const assets: AssetData[] = [{ asset: assetName, amount: 1, avgPrice: '1.00' }]
const { firestore } = setupAssets(assetName, userId, assets)
const result = await assetFuncs.getAssets(firestore, userId, assetName)
t.deepEqual(result.docs, assets)
})
The actual code that gets the assets:
async function getAssets (firestore: firestore.Firestore, userId: string, asset: string) {
const colRef = firestore.collection(`users/${userId}/assets`)
const query = colRef.where('asset', '==', asset)
return query.get()
}
And finally the sinon mocking function that sets up a fake Firestore:
function setupAssets (assetName: string, userId: string, assets: AssetData[]) {
const firestore = sinon.createStubInstance(firestore.Firestore)
const collectionRef = sinon.createStubInstance(firestore.CollectionReference)
const query = sinon.createStubInstance(firestore.Query)
const querySnapshot = sinon.createStubInstance(firestore.QuerySnapshot)
const docSnapshots = assets.map(asset => {
const docSnapshot = sinon.createStubInstance(firestore.DocumentSnapshot)
Object.defineProperty(docSnapshot, 'id', { value: crypto.randomUUID() })
docSnapshot.data.returns(asset)
return docSnapshot
})
Object.defineProperty(querySnapshot, 'docs', { value: docSnapshots })
query.get.resolves(querySnapshot)
collectionRef.where.withArgs('asset', '==', assetName).returns(query)
firestore.collection.withArgs(`users/${userId}/assets`).returns(collectionRef)
return { firestore, collectionRef, query, querySnapshot, docSnapshots }
}
You wouldn't use sinon to mock this, you would make use of the #firebase/rules-unit-testing package to force interaction with a Firestore/RTDB emulator.

Tracking context with async_hooks

I'm trying to track context through the async stack using node async_hooks. It works for most cases, however I have found this use case that I can't think how to resolve:
service.js:
const asyncHooks = require('async_hooks');
class Service {
constructor() {
this.store = {};
this.hooks = asyncHooks.createHook({
init: (asyncId, type, triggerAsyncId) => {
if (this.store[triggerAsyncId]) {
this.store[asyncId] = this.store[triggerAsyncId];
}
},
destroy: (asyncId) => {
delete this.store[asyncId];
},
});
this.enable();
}
async run(fn) {
this.store[asyncHooks.executionAsyncId()] = {};
await fn();
}
set(key, value) {
this.store[asyncHooks.executionAsyncId()][key] = value;
}
get(key) {
const state = this.store[asyncHooks.executionAsyncId()];
if (state) {
return state[key];
} else {
return null;
}
}
enable() {
this.hooks.enable();
}
disable() {
this.hooks.disable();
}
}
module.exports = Service;
service.spec.js
const assert = require('assert');
const Service = require('./service');
describe('Service', () => {
let service;
afterEach(() => {
service.disable();
});
it('can handle promises created out of the execution stack', async () => {
service = new Service();
const p = Promise.resolve();
await service.run(async () => {
service.set('foo');
await p.then(() => {
assert.strictEqual('foo', service.get());
});
});
});
});
This test case will fail because the triggerAsyncId of the promise created when calling next is the executionAsyncId of the Promise.resolve() call. Which was created outside the current async stack and is a separate context. I can't see any way to marry the next functions async context with the context it was created in.
https://github.com/domarmstrong/async_hook_then_example
I wrote a very similar package called node-request-context with a blog post to explain it.
You haven't define any value for foo and you are not asking for any value when calling service.get() without any key. But I guess that was a minor mistake when you wrote the question.
The main issue you named was the location of Promise.resolve. I agree, there is no way to make it work. This is exactly the reason you've create the run function, so you will catch the executionAsyncId and track your code using it. Otherwise, you couldn't track any context.
Your code was just for testing but if you really need, you can cheat by using arrow function:
it('can handle promises created out of the execution stack', async () => {
service = new Service();
const p = () => Promise.resolve();
await service.run(async () => {
service.set('foo', 'bar');
await p().then(() => {
assert.strictEqual('bar', service.get('foo'));
});
});
});
I found a solution, which is not perfect, but does work. Wrapping the original promise with Promise.all will resolve to the correct executionAsyncId. But it does rely on the calling code being aware of the promises context.
const assert = require('assert');
const Service = require('./service');
describe('Service', () => {
let service;
afterEach(() => {
service.disable();
});
it('can handle promises created out of the execution stack', async () => {
service = new Service();
const p = Promise.resolve();
await service.run(async () => {
service.set('foo');
await Promise.all([p]).then(() => {
assert.strictEqual('foo', service.get());
});
});
});
});

Resources