How to mock an EventEmitter on `createReadStream`? - javascript

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;
} }
},
};

Related

Why doesn't promise return from Lambda work while returning stream to handler for uploading to S3?

I want to return a stream from a function for the main Lambda handler to create a pipe. This works:
const { S3Client } = require("#aws-sdk/client-s3")
const { Upload } = require('#aws-sdk/lib-storage')
const stream = require('stream')
const s3Region = 'us-east-1'
const bucketname = "my_bucket"
exports.handler = function (event, context, callback) {
let streamfrom = stream.Readable.from(["four"])
getS3Stream()
.then(streamto => {
stream.pipeline(
streamfrom,
streamto,
() => {
callback(null, { 'statusCode': 200 })
})
})
}
function getS3Stream() {
return new Promise(resolve => {
const pass = new stream.PassThrough()
const upload = new Upload({
client: new S3Client({ region: s3Region }),
params: {
Bucket: bucketname,
Key: "test/test.txt",
Body: pass
}
})
upload.done().then((res, error) => {
if (error) { reject(error) }
console.log("s3 uploaded")
})
resolve(pass)
})
}
But I want the handler function to return a promise instead of using a callback, at which point it no longer works:
exports.handler = async function (event, context) {
return new Promise(resolve => {
let streamfrom = stream.Readable.from(["five5"])
getS3Stream()
.then(streamto => {
stream.pipeline(
streamfrom,
streamto,
() => {
resolve({ 'statusCode': 200 })
})
})
})
}
It returns {"statusCode":200}, but "s3 uploaded" is not printed, and the file does not appear in S3.
Am I misunderstanding something about how to use promises here?
If you want your handler to return a Promise, you have to mark it as async.
I have not found any documentation on this but I believe the runtime caller first checks the handler definition so it know how it should call it.
Something like:
if (handler.constructor.name === 'AsyncFunction') {
result = await handler(event, context)
handleResult(null, result)
} else if (handler.constructor.name === 'Function') {
handler(event, context, handleResult)
} else {}
So, going back to your original code, it can be simply
exports.handler = async function (event, context) {
return new Promise(resolve => {
let streamfrom = stream.Readable.from(["five5"])
getS3Stream()
.then(streamto => {
stream.pipeline(
streamfrom,
streamto,
() => resolve({'statusCode': 200})
)
})
})
}
function getS3Stream() {
return new Promise(resolve => {
const pass = new stream.PassThrough()
const upload = new Upload({
client: new S3Client({region: s3Region}),
params: {
Bucket: bucketname,
Key: "test/test.txt",
Body: pass
}
})
upload.done().then((res, error) => {
if (error) {
reject(error)
}
console.log("s3 uploaded")
resolve(pass)
})
})
}
Inspired by this I came up with a solution that almost meets my qualifications. The function doesn't return just the stream, it also returns the promise. I am still hoping someone will come up with something that works without that crutch.
const { S3Client } = require("#aws-sdk/client-s3")
const { Upload } = require('#aws-sdk/lib-storage')
const stream = require('stream');
const s3Region = 'us-east-1'
const bucketname = "my_bucket"
exports.handler = async function (event, context) {
return new Promise(resolve => {
let streamfrom = stream.Readable.from(["five5"])
getS3Stream()
.then(({streamto, promise})=>{
stream.pipeline(
streamfrom,
streamto,
() => promise.then(()=>resolve({ 'statusCode': 200 }))
)
})
})
}
async function getS3Stream() {
const streamto = new stream.PassThrough()
const s3Client = new S3Client({ region: s3Region })
const upload = new Upload({
client: s3Client,
params: {
Bucket: bucketname,
Key: "test/test.txt",
Body: streamto
}
})
let promise = upload.done()
return({streamto,promise})
}

How to mock sns?

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();
})
});

Jest mock function not working as expected

I'm trying to create a mock for a function createClient where it should return a specific object.
However, for some reason the mock is ignored and it runs the function instead of receiving the mock value.
authorization.js
// some requires here
const createClient = req => {
if (!(req.user && req.user.access_token)) {
throw new Error('Not authorized');
}
...
return { ... }
}
const getUser = async client => { ... }
module.exports = options => {
...
createClient(req) is called here
...
}
authorization.test.js
import authorization from '../../server/middlewares/authorization';
describe('authorization.js', () => {
it('Should do something', async done => {
authorization.createClient = jest.fn(() => ({
client: 'test',
}));
// ACT
const authorizationMiddleware = authorization();
const result = await authorizationMiddleware(someOptions);
// ASSERT
expect(result).toBe('authorized');
done();
});
The error
It seems that the mock for createClient is not working as I wanted to be. It should return the object { client: 'test' }
Your code is incomplete, so I try to give a demo for your case. If you want to mock a private variable in the module scope, the createClient function for your case. You can use rewire package to do that.
E.g.
authorization.js
let createClient = (req) => {
if (!(req.user && req.user.access_token)) {
throw new Error('Not authorized');
}
function getUser() {
return 'real user';
}
return { getUser };
};
const getUser = async (client) => {
return client.getUser();
};
module.exports = (options) => {
const client = createClient(options.req);
return () => getUser(client);
};
authorization.test.js:
const rewire = require('rewire');
describe('61076881', () => {
it('should get user', async () => {
const authorization = rewire('./authorization');
const mClient = { getUser: jest.fn().mockReturnValueOnce('fake user') };
const mCreateClient = jest.fn(() => mClient);
authorization.__set__('createClient', mCreateClient);
const options = { req: { user: { access_token: '123' } } };
const authorizationMiddleware = authorization(options);
const user = await authorizationMiddleware();
expect(user).toEqual('fake user');
expect(mCreateClient).toBeCalledWith(options.req);
expect(mClient.getUser).toBeCalledTimes(1);
});
});
unit test results:
PASS stackoverflow/61076881/authorization.test.js (7.601s)
61076881
✓ should get user (10ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 8.54s, estimated 9s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61076881

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);

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