I wrote a lambda as follows.
handler.js
const aws = require('aws-sdk');
const dynamoDb = new aws.DynamoDB.DocumentClient();
const testHandler = async event => {
// some code
// ...
const user = await getUser(userId)
// ...
// some code
}
const promisify = foo => new Promise((resolve, reject) => {
foo((error, result) => {
if (error) {
reject(error)
} else {
resolve(result)
}
})
})
const getUser = (userId) => promisify(callback =>
dynamoDb.get({
TableName: 'test-table',
Key: {
"PK": `${userId}`,
"SK": `${userId}`
}
}, callback))
.then((user) => {
console.log(`Retrieved user: ${userId}`)
return user
})
module.exports = {
testHandler: testHandler,
getUser: getUser
}
I want to write a unit test for testing the getUser function so I tried the following.
handler.test.js
const handler = require('../handler');
const AWS = require('aws-sdk')
const dynamoDbGetParameterPromise = jest.fn().mockReturnValue({
promise: jest.fn().mockResolvedValue({
PK: 'userId-123', SK: 'userId-123'
})
})
AWS.DynamoDB.DocumentClient = jest.fn().mockImplementation(() => ({
get: dynamoDbGetParameterPromise
}))
describe('test getUser', () => {
beforeEach(() => {
jest.resetModules()
});
test('get user success', async () => {
const user = { PK: 'userId-123', SK: 'userId-123' };
const result = await handler.getUser(userId);
expect(result).toEqual(user);
});
});
The error is as follows.
ConfigError: Missing region in config
105 |
106 | const getUser = (userId) => promisify(callback =>
> 107 | dynamoDb.get({
| ^
108 | TableName: 'test-table',
109 | Key: {
110 | "PK": 'userId-123',
It seems the test still uses the dynamoDb in the handler.js rather than the mocked in the test.
Any ideas on how to wire up the mock correctly to test the function?
Thanks in advance!
You can use jest's auto-mock by adding
jest.mock("aws-sdk");
and then AWS.DynamoDB.DocumentClient will be a mocked class so you'll be able to mock it's implementation. And since we want it's get method to be a function that accepts anything as a first argument (as we won't do anything with it within the mock implementation) and a callback that we're expecting it to have been called with null and user we can mock it like this:
AWS.DynamoDB.DocumentClient.prototype.get.mockImplementation((_, cb) => {
cb(null, user);
});
You could use jest.mock(moduleName, factory, options) to mock aws-sdk module manually.
E.g.
handler.js:
const aws = require('aws-sdk');
const dynamoDb = new aws.DynamoDB.DocumentClient();
const promisify = (foo) =>
new Promise((resolve, reject) => {
foo((error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
const getUser = (userId) =>
promisify((callback) =>
dynamoDb.get(
{
TableName: 'test-table',
Key: {
PK: `${userId}`,
SK: `${userId}`,
},
},
callback,
),
).then((user) => {
console.log(`Retrieved user: ${userId}`);
return user;
});
module.exports = { getUser };
handler.test.js:
const aws = require('aws-sdk');
const { getUser } = require('./handler');
jest.mock('aws-sdk', () => {
const mDocumentClient = { get: jest.fn() };
const mDynamoDB = { DocumentClient: jest.fn(() => mDocumentClient) };
return { DynamoDB: mDynamoDB };
});
const mDynamoDb = new aws.DynamoDB.DocumentClient();
describe('64564233', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should get user', async () => {
const mResult = { name: 'teresa teng' };
mDynamoDb.get.mockImplementationOnce((_, callback) => callback(null, mResult));
const actual = await getUser(1);
expect(actual).toEqual({ name: 'teresa teng' });
expect(mDynamoDb.get).toBeCalledWith(
{
TableName: 'test-table',
Key: {
PK: '1',
SK: '1',
},
},
expect.any(Function),
);
});
it('should handler error', async () => {
const mError = new Error('network');
mDynamoDb.get.mockImplementationOnce((_, callback) => callback(mError));
await expect(getUser(1)).rejects.toThrowError('network');
expect(mDynamoDb.get).toBeCalledWith(
{
TableName: 'test-table',
Key: {
PK: '1',
SK: '1',
},
},
expect.any(Function),
);
});
});
unit test result:
PASS src/stackoverflow/64564233/handler.test.js (14.929s)
64564233
✓ should get user (23ms)
✓ should handler error (3ms)
console.log src/stackoverflow/64564233/handler.js:433
Retrieved user: 1
------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
handler.js | 100 | 100 | 100 | 100 | |
------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 17.435s
source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/64564233
Related
I have written a test code to test code that gives credentials from AWS Secret Manager. I used proxyquire and sinon for stubbing and getting this error.
Function I want to test
exports.getCredsFromAWSSecretsManager = (keyName) => {
const SM = new AWS.SecretsManager({
apiVersion: process.env.AWS_SM_API_VERSION,
region: process.env.AWS_SM_REGION
});
return SM.getSecretValue(params).promise().then((data) => {
logger.info(logMsgs.awsHlpr_smGetSecretValueSuccess(JSON.stringify(data)));
return JSON.parse(data.SecretString);
}).catch((err) => {
logger.error(logMsgs.awsHlpr_smGetSecretValueErr(JSON.stringify(err)));
throw err;
});
};
Test case that I have written
const sinon = require("sinon");
const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
const { mockLogger } = require("../../mockdata/mockLogger");
let awsHelper;
let secretsManagerStub;
describe.only("AWS Helper ", () => {
// function1
describe("AWS Helper: getCredsFromAWSSecretsManagera method", () => {
before((done) => {
const data = {
SecretString: JSON.stringify({ publicKey: 'secretUsername', privateKey: 'secretPassword' }),
};
secretsManagerStub = {
getSecretValue: sinon.stub().callsFake((params, callback) => {
callback(null, data);
}),
};
const awsStub = {
SecretsManager: sinon.stub().returns(secretsManagerStub)
}
awsHelper = proxyquire('../../../utils/aws_helper.js', {
'aws-sdk':{
AWS:awsStub
} ,
"../../utils/logger": mockLogger,
});
done();
});
afterEach(() => {
sinon.restore();
});
it('should write random data!', async () => {
const expectedData = "abcdef";
secretsManagerStub.getSecretValue.yields(null, expectedData);
const data = await awsHelper.getCredsFromAWSSecretsManager();
sinon.assert.callCount(secretsManagerStub.getSecretValue, 1);
assert.strictEqual(data, expectedData);
});
});
});
This code gives me the error saying
TypeError: AWS.SecretsManager is not a constructor
any help would be greatly appreciated.
AWS is a namespace, it contains all AWS service classes like SecretsManager. You should provide the awsStub to aws-sdk, there is no need to wrap the awsStub inside an object.
aws_helper.js:
const AWS = require('aws-sdk');
exports.getCredsFromAWSSecretsManager = () => {
const SM = new AWS.SecretsManager({
apiVersion: process.env.AWS_SM_API_VERSION,
region: process.env.AWS_SM_REGION,
});
const params = {
SecretId: '1',
};
return SM.getSecretValue(params)
.promise()
.then((data) => {
console.info(data);
return JSON.parse(data.SecretString);
})
.catch((err) => {
console.error(err);
throw err;
});
};
aws_helper.test.js:
const sinon = require('sinon');
const proxyquire = require('proxyquire').noCallThru().noPreserveCache();
let awsHelper;
let secretsManagerStub;
describe('AWS Helper: getCredsFromAWSSecretsManagera method', () => {
before(() => {
const data = {
SecretString: JSON.stringify({ publicKey: 'secretUsername', privateKey: 'secretPassword' }),
};
secretsManagerStub = {
getSecretValue: sinon.stub().returnsThis(),
promise: sinon.stub().resolves(data),
};
const awsStub = {
SecretsManager: sinon.stub().returns(secretsManagerStub),
};
awsHelper = proxyquire('./aws_helper.js', {
'aws-sdk': awsStub,
});
});
afterEach(() => {
sinon.restore();
});
it('should write random data!', async () => {
const data = await awsHelper.getCredsFromAWSSecretsManager();
sinon.assert.callCount(secretsManagerStub.getSecretValue, 1);
sinon.assert.match(data, { publicKey: 'secretUsername', privateKey: 'secretPassword' });
});
});
test result:
AWS Helper: getCredsFromAWSSecretsManagera method
{
SecretString: '{"publicKey":"secretUsername","privateKey":"secretPassword"}'
}
✓ should write random data!
1 passing (2s)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 77.78 | 100 | 66.67 | 77.78 |
aws_helper.js | 77.78 | 100 | 66.67 | 77.78 | 19-20
---------------|---------|----------|---------|---------|-------------------
The following line in my unit test:
sandbox.stub(myAPI,'getPeople').returns([500, {errorCode: '500'}])
is giving me the following error:
TypeError: Cannot stub non-existent property getPeople
I'm trying to stub an error response from my API call to test my error handling.
The unit test:
const chai = require('chai');
const sinonChai = require('sinon-chai');
const sinon = require('sinon');
const { getPeopleHandler } = require('../services/handlers/get-people-handler');
const { expect } = chai;
const myAPI = require('../services/api');
chai.use(sinonChai);
describe('handle error', () => {
let req;
let res;
let sandbox;
describe('getPeopleHandler() with error', () => {
before(() => {
req = {
session: {}
};
res = {
render: () => ({})
};
sandbox = sinon.createSandbox();
});
beforeEach(() => {
sandbox.stub(myAPI,'getPeople').returns([500, {errorCode: '500'}]);
})
afterEach(() => {
sandbox.restore();
});
it('should render the error page', async () => {
sandbox.stub(res, 'render').returns({});
res.locals = {};
await getPeopleHandler(req, res);
expect(res.render).to.have.been.calledOnceWith('error.html');
});
});
});
api.js
const fetch = require('node-fetch');
const getPeople = (url) => {
console.log(`About to call API at ${url}`);
return new Promise((resolve, reject) => {
fetch(url, { method: 'GET' })
.then(res => Promise.all([res.status, res.json()]))
.then(([status, jsonData]) => {
resolve([status, jsonData]);
}).catch(error => {
console.log('e', error)
reject(error)
})
})
};
module.exports = getPeople;
get-people-handler.js
const callAPI = require('../../services/api');
const config = require('../../config/config');
const getPeopleHandler = async (req, res) => {
const url = config.getPeopledataUrl;
const response = await callAPI(url);
console.log(`API call status = ${response[0]}`);
if (response[0] === 200) {
res.locals.list = response[1];
res.render('people.html');
} else {
res.locals.error = response[0];
res.render('error.html');
}
};
module.exports = { getPeopleHandler };
You are stub the getPeople function because you use module.exports. If you want to export a object, you should do it like this:
module.exports = { getPeople };
sinon.stub(object, "method")
Replaces object.method with a stub function. An exception is thrown if the property is not already a function.
Below is a working example:
api.js:
const fetch = require('node-fetch');
const getPeople = (url) => {
console.log(`About to call API at ${url}`);
return new Promise((resolve, reject) => {
fetch(url, { method: 'GET' })
.then((res) => Promise.all([res.status, res.json()]))
.then(([status, jsonData]) => {
resolve([status, jsonData]);
})
.catch((error) => {
console.log('e', error);
reject(error);
});
});
};
module.exports = { getPeople };
config.js:
module.exports = {
getPeopledataUrl: 'http://localhost:3000/api/people',
};
get-people-handler.js:
const api = require('./api');
const config = require('./config');
const getPeopleHandler = async (req, res) => {
const url = config.getPeopledataUrl;
const response = await api.getPeople(url);
console.log(`API call status = ${response[0]}`);
if (response[0] === 200) {
res.locals.list = response[1];
res.render('people.html');
} else {
res.locals.error = response[0];
res.render('error.html');
}
};
module.exports = { getPeopleHandler };
get-people-handler.test.js:
const sinon = require('sinon');
const { getPeopleHandler } = require('./get-people-handler');
const myAPI = require('./api');
describe('handle error', () => {
let req;
let res;
let sandbox;
describe('getPeopleHandler() with error', () => {
before(() => {
req = {
session: {},
};
res = {
render: () => ({}),
};
sandbox = sinon.createSandbox();
});
beforeEach(() => {
sandbox.stub(myAPI, 'getPeople').returns([500, { errorCode: '500' }]);
});
afterEach(() => {
sandbox.restore();
});
it('should render the error page', async () => {
sandbox.stub(res, 'render').returns({});
res.locals = {};
await getPeopleHandler(req, res);
sinon.assert.calledWithExactly(res.render, 'error.html');
});
});
});
test result:
handle error
getPeopleHandler() with error
API call status = 500
✓ should render the error page
1 passing (7ms)
-----------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------------|---------|----------|---------|---------|-------------------
All files | 60.87 | 50 | 16.67 | 60.87 |
api.js | 30 | 100 | 0 | 30 | 4-13
config.js | 100 | 100 | 100 | 100 |
get-people-handler.js | 83.33 | 50 | 100 | 83.33 | 9-10
-----------------------|---------|----------|---------|---------|-------------------
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 |
----------|---------|----------|---------|---------|-------------------
I am trying to implement gcloud-storage with nodejs and test them using typescript
This is my actual class
Please do not consider the logging implementation for now.
The storage is authenticated by an external service call -
const str =
GcloudAuthenticationInstance.createGcloudAuthenticationBucket();
and the file that I am willing to store in gcloud is manipulated using streams , with the pump module
export const uploadEnvFiles = async (env_name: string) => {
const LOGGER: pino.Logger = PinoLoggerServiceInstance.getLogger(__filename);
return new Promise(async (res, rej) => {
const str = GcloudAuthenticationInstance.createGcloudAuthenticationBucket();
const bucketToUpload = GCLOUD_ENV_STR_BUCKET_NAME;
let uploadLocalFilePath;
let destinationBucketPath;
if (!AppUtilServiceInstance.isNullOrUndefined(env_name)) {
uploadLocalFilePath = ENV_NAME_DEV === env_name ? GCLOUD_UPLOAD_FILE_DEV_LOCAL_PATH : GCLOUD_UPLOAD_FILE_PROD_LOCAL_PATH;
destinationBucketPath = ENV_NAME_DEV === env_name ? GCLOUD_DATABASE_BUCKET_DEV : GCLOUD_DATABASE_BUCKET_PROD;
}
LOGGER.info('after authentication');
pump(
fs.createReadStream(uploadLocalFilePath),
str
.bucket(bucketToUpload)
.file(destinationBucketPath)
.createWriteStream({
gzip: true,
public: true,
resumable: true,
})
)
.on('error', (err) => {
LOGGER.error('Error occured in uploading:', err);
rej({ status: 'Error', error: err, code: 500 });
})
.on('finish', () => {
LOGGER.info('Successfully uploaded the file');
res({ status: 'Success', code: 201, error: null });
});
});
};
Now there are possibilities of the stream finishing or erroring out and I wanted to test both.
I am able to mock the pump npm module as a whole with jest.mock like this hoisted at the top before any test suite declarations.
jest.mock('pump', () =>
jest.fn().mockImplementation(() => {
const readStream = fs.createReadStream(
path.resolve(process.cwd(), './tests/cloud-storage/sample-read.txt')
);
const writeStream = fs.createWriteStream(
path.resolve(process.cwd(), './tests/cloud-storage/sample-write.txt')
);
return readStream.pipe(writeStream);
})
);
So the above is an implementation for the working scenario, where I have piped an existing file to an output stream and returned the stream, making the mock of pump to work. Here is my test spec file
const globalAny: any = global;
describe('Test suite for bucket functionality', () => {
beforeEach(() => {
jest.restoreAllMocks();
});
afterAll(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
jest.resetAllMocks();
});
test('test upload - make the actual call', async (done) => {
// to make sure that mock fs doesnt affect the gcloud authentication, this is a MUST
const createGcloudAuthenticationBucketSpy = jest
.spyOn(GcloudAuthenticationInstance, 'createGcloudAuthenticationBucket')
.mockImplementation(() => {
return new Storage();
});
const res = BucketOperations.uploadEnvFiles(globalAny.ENV_JEST);
await expect(res).resolves.toBeDefined();
expect(createGcloudAuthenticationBucketSpy).toHaveBeenCalledTimes(1);
done();
});
});
Now this works with the mocked pump call. But I wanted to test the scenario where the stream emits error as well in the same spec. Is there a possibility to overwrite the mockImplementation in another test spec. Since this is a npm module, I have written the jest.mock() at the top which will serve as the mock for the entire test suite, but unsure as to how to overwrite it. I've been trying for past 3 days and couldn't figure it out. Any way that can be achieved?
Here is the unit test solution using jest.mock(moduleName, factory, options) and jest.spyOn(object, methodName).
bucketOperations.ts:
import fs from 'fs';
import pump from 'pump';
import { GcloudAuthenticationInstance } from './gcloudAuthenticationInstance';
import { AppUtilServiceInstance } from './appUtilServiceInstance';
const {
GCLOUD_ENV_STR_BUCKET_NAME,
GCLOUD_UPLOAD_FILE_DEV_LOCAL_PATH,
GCLOUD_UPLOAD_FILE_PROD_LOCAL_PATH,
GCLOUD_DATABASE_BUCKET_DEV,
GCLOUD_DATABASE_BUCKET_PROD,
ENV_NAME_DEV,
} = process.env;
export const uploadEnvFiles = async (env_name: string) => {
return new Promise(async (res, rej) => {
const str = GcloudAuthenticationInstance.createGcloudAuthenticationBucket();
const bucketToUpload = GCLOUD_ENV_STR_BUCKET_NAME;
let uploadLocalFilePath;
let destinationBucketPath;
if (!AppUtilServiceInstance.isNullOrUndefined(env_name)) {
uploadLocalFilePath =
ENV_NAME_DEV === env_name ? GCLOUD_UPLOAD_FILE_DEV_LOCAL_PATH : GCLOUD_UPLOAD_FILE_PROD_LOCAL_PATH;
destinationBucketPath = ENV_NAME_DEV === env_name ? GCLOUD_DATABASE_BUCKET_DEV : GCLOUD_DATABASE_BUCKET_PROD;
}
console.info('after authentication');
pump(
fs.createReadStream(uploadLocalFilePath),
str
.bucket(bucketToUpload)
.file(destinationBucketPath)
.createWriteStream({
gzip: true,
public: true,
resumable: true,
}),
)
.on('error', (err) => {
console.error('Error occured in uploading:', err);
rej({ status: 'Error', error: err, code: 500 });
})
.on('finish', () => {
console.info('Successfully uploaded the file');
res({ status: 'Success', code: 201, error: null });
});
});
};
appUtilServiceInstance.ts:
const AppUtilServiceInstance = {
isNullOrUndefined: (env_name) => typeof env_name === 'undefined',
};
export { AppUtilServiceInstance };
gcloudAuthenticationInstance.ts:
const GcloudAuthenticationInstance = {
createGcloudAuthenticationBucket: () => {
const storage = {
bucket(name) {
return this;
},
file(filename) {
return this;
},
createWriteStream(options) {
return 'write stream';
},
};
return storage;
},
};
export { GcloudAuthenticationInstance };
bucketOperations.test.ts:
import pump from 'pump';
import fs from 'fs';
import { GcloudAuthenticationInstance } from './gcloudAuthenticationInstance';
jest.mock('pump', () => {
const mPump = { on: jest.fn() };
return jest.fn(() => mPump);
});
describe('61031410', () => {
let originalEnv;
beforeEach(() => {
originalEnv = process.env;
});
afterEach(() => {
process.env = originalEnv;
jest.restoreAllMocks();
});
it('should upload file correctly', async () => {
process.env.ENV_NAME_DEV = 'dev';
process.env.GCLOUD_ENV_STR_BUCKET_NAME = 'bucket-dev';
process.env.GCLOUD_UPLOAD_FILE_DEV_LOCAL_PATH = 'dev';
process.env.GCLOUD_DATABASE_BUCKET_DEV = 'bucket-dev-db';
const BucketOperations = require('./bucketOperations');
const createReadStreamSpy = jest.spyOn(fs, 'createReadStream').mockReturnValueOnce('rs' as any);
const mStorage: any = {
bucket: jest.fn().mockReturnThis(),
file: jest.fn().mockReturnThis(),
createWriteStream: jest.fn().mockReturnValueOnce('ws'),
};
const infoSpy = jest.spyOn(console, 'info');
const createGcloudAuthenticationBucketSpy = jest
.spyOn(GcloudAuthenticationInstance, 'createGcloudAuthenticationBucket')
.mockReturnValueOnce(mStorage);
pump().on.mockImplementation(function(this: any, event, callback) {
if (event === 'finish') {
callback();
}
return this;
});
const actual = await BucketOperations.uploadEnvFiles('dev');
expect(actual).toEqual({ status: 'Success', code: 201, error: null });
expect(createGcloudAuthenticationBucketSpy).toBeCalledTimes(1);
expect(pump).toBeCalledWith('rs', 'ws');
expect(createReadStreamSpy).toBeCalledWith('dev');
expect(mStorage.bucket).toBeCalledWith('bucket-dev');
expect(mStorage.file).toBeCalledWith('bucket-dev-db');
expect(mStorage.createWriteStream).toBeCalledWith({ gzip: true, public: true, resumable: true });
expect(infoSpy.mock.calls[0]).toEqual(['after authentication']);
expect(infoSpy.mock.calls[1]).toEqual(['Successfully uploaded the file']);
});
it('should handle the error if upload file failure', () => {
// TODO: you can do this like above
});
});
unit test results with coverage report:
PASS stackoverflow/61031410/bucketOperations.test.ts (7.94s)
61031410
✓ should upload file correctly (69ms)
✓ should handle the error if upload file failure
console.info node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866
after authentication
console.info node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866
Successfully uploaded the file
---------------------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------------------------|---------|----------|---------|---------|-------------------
All files | 80.56 | 50 | 54.55 | 79.41 |
appUtilServiceInstance.ts | 100 | 100 | 100 | 100 |
bucketOperations.ts | 92.31 | 50 | 83.33 | 91.67 | 40,41
gcloudAuthenticationInstance.ts | 28.57 | 100 | 0 | 28.57 | 3,5,8,11,14
---------------------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 9.247s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61031410
Here is my function
async function remoteCSVToJSON<T>(url: string, options: csv.ConverterOptions) {
const defaultOptions = {
noheader: false,
delimiter: ',',
workerNum: os.cpus().length,
};
const finalOptions = defaultsDeep(options, defaultOptions);
const datas: T[] = [];
return new Promise<T[]>((resolve, reject) => {
request.get(url).then(res => {
csv(finalOptions)
.fromString(res)
.on('json', (jsonObj: T) => datas.push(jsonObj))
.on('error', err => reject(err))
.on('end', () => {
logger.info('convert csv to json done');
resolve(datas);
});
});
});
}
I can mock fromString for csvtojson module like this:
jest.mock('csvtojson', () => {
return {
__esModule: true,
fromString: jest.fn(),
};
});
But how do I mock the .on(event) method and event handlers?
Here is the unit test solution:
index.ts:
import request from 'request-promise';
import csv from 'csvtojson';
import os from 'os';
import { defaultsDeep } from 'lodash';
export async function remoteCSVToJSON<T>(url: string, options: any) {
const defaultOptions = {
noheader: false,
delimiter: ',',
workerNum: os.cpus().length
};
const finalOptions = defaultsDeep(options, defaultOptions);
const datas: T[] = [];
return new Promise<T[]>((resolve, reject) => {
request.get(url).then(res => {
csv(finalOptions)
.fromString(res)
.on('json', (jsonObj: T) => datas.push(jsonObj))
.on('error', err => reject(err))
.on('end', () => {
console.info('convert csv to json done');
resolve(datas);
});
});
});
}
index.spec.ts:
import request from 'request-promise';
import csv from 'csvtojson';
import os from 'os';
import { remoteCSVToJSON } from './';
jest.mock('csvtojson', () => {
const mCsv = {
on: jest.fn(),
fromString: jest.fn().mockReturnThis()
};
return jest.fn(() => mCsv);
});
jest.mock('request-promise', () => {
return {
get: jest.fn()
};
});
describe('remoteCSVToJSON', () => {
afterEach(() => {
jest.restoreAllMocks();
});
test('should request and convert csv to json', async () => {
const events: any = {};
csv()
.fromString()
.on.mockImplementation(function(this: any, ...args: any[]) {
const [event, handler] = args;
events[event] = handler;
return this;
});
const mGetResponse = { data: 'fake data' };
(request.get as jest.MockedFunction<typeof request.get>).mockResolvedValueOnce(mGetResponse);
const infoSpy = jest.spyOn(console, 'info');
const actualValue = remoteCSVToJSON('url', {});
const mJsonObj = { id: 1, name: 'mrdulin' };
expect(jest.isMockFunction(csv)).toBeTruthy();
await new Promise(resolve => setTimeout(resolve, 0));
expect(csv).toBeCalledWith({
noheader: false,
delimiter: ',',
workerNum: os.cpus().length
});
expect(csv().fromString).toBeCalledWith(mGetResponse);
events['json'](mJsonObj);
events['end']();
await expect(actualValue).resolves.toEqual([mJsonObj]);
expect(request.get).toBeCalledWith('url');
expect(infoSpy).toBeCalledWith('convert csv to json done');
});
test('should throw error when convert error', async () => {
const events: any = {};
csv()
.fromString()
.on.mockImplementation(function(this: any, ...args: any[]) {
const [event, handler] = args;
events[event] = handler;
return this;
});
const mGetResponse = { data: 'fake data' };
(request.get as jest.MockedFunction<typeof request.get>).mockResolvedValueOnce(mGetResponse);
const actualValue = remoteCSVToJSON('url', {});
const mJsonObj = { id: 1, name: 'mrdulin' };
expect(jest.isMockFunction(csv)).toBeTruthy();
await new Promise(resolve => setTimeout(resolve, 0));
expect(csv).toBeCalledWith({
noheader: false,
delimiter: ',',
workerNum: os.cpus().length
});
expect(csv().fromString).toBeCalledWith(mGetResponse);
events['json'](mJsonObj);
const mError = new Error('mock error');
events['error'](mError);
await expect(actualValue).rejects.toThrowError(mError);
expect(request.get).toBeCalledWith('url');
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/57423762/index.spec.ts (6.995s)
remoteCSVToJSON
✓ should request and convert csv to json (16ms)
✓ should throw error when convert error (22ms)
console.info node_modules/jest-mock/build/index.js:860
convert csv to json done
----------|----------|----------|----------|----------|-------------------|
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: 8.114s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57423762