How to mock sns? - javascript

I faced to some issue when I wanted to test my handler which publishes a message on SNS.
Here is my code:
// my handler
export const handler = (event) => {
try {
await emitDeletionComplete(classified.metadata.classifiedRequestId);
}
catch(e) {
console.error(e);
throw new Error(e);
}
}
// my SNS service
import SNS from 'aws-sdk/clients/sns';
const snsClient = new SNS({region: process.env.AWS_REGION});
export const emitDeletionComplete = async (id) => {
try {
await snsClient.publish({
Message: JSON.stringify({
type: 'DELETE_COMPLETE',
data: {
id
}
}),
TopicArn: process.env.SNS_ARN
}).promise();
} catch(err) {
console.error(err, err.stack);
throw new Error('We do not succeed to publish the message DELETE_COMPLETE to ARN: ' + process.env.SNS_ARN);
}
};
When i want to test, i try to do :
import { handler } from '../../../src/handler/dispatch-deletion-to-legacy';
import SNS from 'aws-sdk/clients/sns';
jest.mock('aws-sdk/clients/sns', () => {
return {
__esModule: true,
default: jest.fn(() => {
return {
publish: jest.fn().mockReturnThis(),
promise: jest.fn(),
}
}),
};
});
[...]
it('should delete', () => {
let sns = new SNS();
const event = {
Records: [
{
body: JSON.stringify({...some event...})
}
]
}
handler(event);
expect(sns.publish().promise).toBeCalledTimes(1);
});
Apparently, it is never called. I don't get why.Maybe my mock is completely wrong.
i'm stuck with it for few hours now...Any idea how can I mock correctly ?
EDIT 1 : https://github.com/JLGouwy/aws-sns-mock-test
thanks

In short, your instance - let sns = new SNS(); is not instance used by your production code.
In long, you have keeping track of usage of sns mock instance, jest document
This is your example, but I changed a little. I will try to explain by comments in bellow example.
Production code:
import SNS from 'aws-sdk/clients/sns';
const snsClient = new SNS({ region: process.env.AWS_REGION });
exports.handler = async (event) => {
try {
const { classifiedRequestId } = JSON.parse(event.Records[0].body); // try to get classifiedRequestId from Record body
await emitDeletionComplete(classifiedRequestId);
} catch (e) {
console.error(e);
throw new Error(e);
}
}
const emitDeletionComplete = async (id) => { // in the same file with handler function
try {
await snsClient.publish({
Message: JSON.stringify({
type: 'DELETE_COMPLETE',
data: {
id
}
}),
TopicArn: process.env.SNS_ARN
}).promise();
} catch (err) {
console.error(err, err.stack);
throw new Error('We do not succeed to publish the message DELETE_COMPLETE to ARN: ' + process.env.SNS_ARN);
}
};
Spec file
import SNS from 'aws-sdk/clients/sns';
import { handler } from "."; // handler function in the same directory
const mockPromise = jest.fn(); // mock for deep function - promise
jest.mock('aws-sdk/clients/sns', () => {
// return a function as a constructor
return jest.fn().mockImplementation(function () { // "normal function" not arrow function
this.publish = jest.fn(() => ({ // mock publish function
promise: mockPromise, // returns an object what includes promise property
}));
});
});
describe("handler function", () => {
it("should publish delete command", async () => {
const classifiedRequestId = "12345";
const event = {
Records: [
{
body: JSON.stringify({ // input data
classifiedRequestId,
}),
}
]
};
await handler(event);
// get instance of SNS, an instance has been created in production code
const snsMocked = SNS.mock.instances[0];
// expect publish function will be call with expected parameter
expect(snsMocked.publish).toHaveBeenCalledWith(
{
Message: JSON.stringify({
type: 'DELETE_COMPLETE',
data: {
id: classifiedRequestId,
}
}),
TopicArn: process.env.SNS_ARN
}
);
// expect promise function will be call too
expect(mockPromise).toHaveBeenCalled();
})
});

Related

Jest does not detect my mock call in promise .catch block

Jest does not seem to be detecting my mock calls in the catch block. Yet, if don't mock the method handleError, I'll receive an error. I have tried multiple ways of doing the returned rejected promise but no luck so far.
I've tried callbacks, Promise.reject, Promise((res, rej) => {})
Code being tested:
module.exports = class PresetDropdown {
constructor (Api, objectId, titleSort) {
this.Api = Api;
this.objectId; = objectId;
this.presets = [];
}
handleError (err) {
console.log(err)
// more functionality
}
get () {
this.Api.getMany(this.objectId)
.then((data) => {
this.handleEmpty(data);
this.isError = false;
})
.catch((err) => {
this.isError = true;
this.handleError(err);
});
}
};
Test (Jest):
test('Expect call from handleError after reject promise', async () => {
// assemble
// return the class
const Module = getModule();
const mockRejectedPromise = jest.fn(() => {
return Promise.reject(Error(mockError));
});
mockApi.getMany = mockRejectedPromise;
const module = new Module(mockApi, '1', null);
const mockHandleError = jest.fn(() => {});
PresetTemplate.handleError = mockHandleError;
// act
await PresetTemplate.get(mockScope);
// assert
expect(mockHandleError).toHaveBeenCalledTimes(1); // DOES NOT DETECT CALL
// Test CASE FAILS HERE ^
});
Since PresetDropdown class accept an Api object, you can create a mocked Api object and pass it to the class. Then mock resolved/rejected value for Api.getMany() method, so that you can test different code branches.
E.g.
presetDropdown.js:
module.exports = class PresetDropdown {
constructor(Api, objectId, titleSort) {
this.Api = Api;
this.objectId = objectId;
this.presets = [];
}
handleError(err) {
console.log(err);
}
handleEmpty(data) {
console.log(data);
}
get() {
return this.Api.getMany(this.objectId)
.then((data) => {
this.handleEmpty(data);
this.isError = false;
})
.catch((err) => {
this.isError = true;
this.handleError(err);
});
}
};
presetDropdown.test.js:
const PresetDropdown = require('./presetDropdown');
describe('71164955', () => {
test('should handle data', async () => {
const mockApi = {
getMany: jest.fn().mockResolvedValueOnce('fake data'),
};
const instance = new PresetDropdown(mockApi, '1');
await instance.get();
expect(instance.isError).toBeFalsy();
});
test('should handle error', async () => {
const mockError = new Error('fake error');
const mockApi = {
getMany: jest.fn().mockRejectedValueOnce(mockError),
};
const instance = new PresetDropdown(mockApi, '1');
await instance.get();
expect(instance.isError).toBeTruthy();
});
});
Test result:
PASS stackoverflow/71164955/presetDropdown.test.js
71164955
✓ should handle data (14 ms)
✓ should handle error (3 ms)
console.log
fake data
at PresetDropdown.handleEmpty (stackoverflow/71164955/presetDropdown.js:11:13)
console.log
Error: fake error
at Object.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/stackoverflow/71164955/presetDropdown.test.js:12:23)
at Object.asyncJestTest (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)
at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:45:12
at new Promise (<anonymous>)
at mapper (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:28:19)
at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:75:41
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at PresetDropdown.handleError (stackoverflow/71164955/presetDropdown.js:8:13)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.311 s

await not waiting for child function to execute first in react

async fetchJobs() {
this.setState({ isFetching: true }, async () => {
try{
debugger;
console.log("fetching Jobs");
var body = {
page: this.state.page,
sortBy: this.state.sortBy,
comparator: this.state.comparator,
batch: this.state.batch,
role: this.state.role,
companies: this.state.selectedCompanies
}
var job = await axios({
method: 'get',
url: `${process.env.PUBLIC_URL}/api/page_job?page=${this.state.page}`,
params: body
});
const page_jobs = job.data.page;
const jc = job.data.count;
const jobcount = parseInt(jc);
this.setState({
jobs: page_jobs,
jobcount: jobcount
}, () => {
this.getPagination();
if (this.refJobs.current)
this.refJobs.current.scrollTop = 0;
});
debugger;
console.log("fetched jobs");
}
catch(error){
console.log("err1");
throw error;
}
finally{
this.setState({ isFetching: false });
}
});
}
filterHandler = async (body) => {
this.setState({
page: 1,
sortBy: body.sortBy,
comparator: body.comparator,
batch: body.batch,
role: body.role,
selectedCompanies: body.selectedCompanies
}, async () => {
tr{
await this.fetchJobs();
console.log("not catching error");
}
catch(error){
console.log("err2");
throw error;
}
})
}
When filterHandler function is called through await it is giving output as:
fetching jobs
not catching error
fetched jobs,
instead of:
fetching jobs
fetched jobs
not catching error
I am not able to understand how to use async/await to get the desired output. As async/await should
have stopped the parent function, executed the child and then returned to the parent function.
When you await fetchJobs, you are not awaiting the fetch Promise.
Try this:
async fetchJobs() {
this.setState({ isFetching: true });
try {
// ...
}
catch(error) {
// ...
}
finally {
// ...
}
}
Another option is to explicitly generate and resolve a Promise:
fetchJobs = () => new Promise( (resolve, reject) => {
this.setState({ isFetching: true }, async () => {
try {
// ...
debugger;
console.log("fetched jobs");
resolve(jobcount); // For example...
}
catch(error) {
// ...
reject(error);
}
finally {
// Not sure if this is going to be executed, probably not
this.setState({ isFetching: false });
}
});
})

module.exports returning undefined

I`m trying to make a translation function with IBM Watson API in "services/ibmWatson/index.js". I receive the response correctly, but when I return the response to the "IncidentController.js" it receives as undefined.
const LanguageTranslatorV3 = require('ibm-watson/language-translator/v3');
const { IamAuthenticator } = require('ibm-watson/auth');
module.exports = {
async translate(phrase, language) {
const languageTranslator = new LanguageTranslatorV3({
authenticator: new IamAuthenticator({ apikey: '<my_API_key>' }),
url: 'https://gateway.watsonplatform.net/language-translator/api/',
version: '2020-03-28',
});
await languageTranslator.translate(
{
text: phrase,
source: 'pt',
target: language
})
.then(response => {
if(response.status=200){
console.log(response.result.translations);
return(response.result.translations);
}
return (["error"]);
})
.catch(err => {
console.log('error: ', err);
return (["error"]);
});
}
}
In the above code the console.log(response.result.translations) returns correctly:
[ { translation: 'Dog run over.' },
{ translation: 'Castration' },
{ translation: 'Ticks' },
{ translation: 'Tuberculosis' } ]
In the in IncidentController.js:
const Watson = require('../../src/services/ibmWatson');
const db_connection = require('../database/connection');
module.exports = {
async index(request, response) {
const incidents = await db_connection('incidents').join('ongs', 'ongs.id', '=', 'incidents.ong_id')
.select([
'incidents.*',
'ongs.name',
'ongs.uf']
);
const titles = [];
incidents.forEach((incident) => { titles.push(incident.title) });
const translated_titles = await Watson.translate(titles, "en");
console.log(translated_titles);
return response.json(incidents);
}
}
In the above code the console.log(response.result.translations) returns undefined.
What is wrong with it?
You are returning response.result.translations to the response callback from then().
Since that callback cannot be accessed by your IncidentController, it returns undefined.
This is one way to solve this problem:
// services/ibmWatson/index.js
translate(phrase, language) {
return new Promise((resolve, reject) => {
try {
const response = await languageTranslator.translate({ /* options */ });
resolve(response); // Return the translations
} catch(error) {
reject(error); // If there's an error, return the error
}
});
}
// IncidentController.js
async index() {
// ...
const translatedTitles = await Watson.translate(titles, "en");
console.log(translatedTitles); // Should be working now
}
I hope I could help you or at least lead you in the right direction.

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 to mock an EventEmitter on `createReadStream`?

I'm trying to write a test case for when an error is emitted as a result of this code:
s3.getObject({
Bucket: mediaBucket,
Key: mediaId,
}).createReadStream()
I've got a test with a dummy S3 object, and I'm using MemoryStream to cover cases where the call is successful. How do I emit an error, so I can write a test that allows me to test the behavior in .on('error') function(error)..?
Here's what I've tried, without success:
beforeEach(function () {
var emitter = new EventEmitter;
const s3 = {
getObject: () => {
return { createReadStream: () => emitter.emit('error', new Error('Random error!')) }
},
};
Just emit it like you would any other event:
stream.emit('error', new Error('Random error!'));
So, the above code should look like:
const s3 = {
getObject: () => {
return { createReadStream: () => {
process.nextTick(function() {
emitter.emit('error', new Error('whoops!'));
});
return emitter;
} }
},
};

Categories

Resources