How can I stub the redis publish method?
// module ipc
const redis = require('redis');
module.exports = class IPC {
constructor() {
this.pub = redis.createClient();
}
publish(data) {
this.pub.publish('hello', JSON.stringify(data));
}
}
and another module
// module service
module.exports = class Service {
constructor(ipc) {
this.ipc = ipc;
}
sendData() {
this.ipc.publish({ data: 'hello' })
}
}
How could I stub the private variable pub in IPC class?
I could stub the redis.createClient by using proxyquire, if I do that it will complain publish undefined
My current test code
let ipcStub;
before(() => {
ipcStub = proxyquire('../ipc', {
redis: {
createClient: sinon.stub(redis, 'createClient'),
}
})
});
it('should return true', () => {
const ipc = new ipcStub();
const ipcPublishSpy = sinon.spy(ipc, 'publish')
const service = new Service(ipc);
service.sendData();
assert.strictEqual(true, ipcPublishSpy.calledOnce);
})
You just need to set the spy on the publish method, no need for the proxyquire.
e.g.
import {expect} from 'chai';
import sinon from 'sinon';
class IPC {
constructor() {
this.pub = {
publish:() => {} //here your redis requirement
};
}
publish(data) {
this.pub.publish('hello', JSON.stringify(data));
}
}
class Service {
constructor(ipc) {
this.ipc = ipc;
}
sendData() {
this.ipc.publish({ data: 'hello' })
}
}
describe('Test Service', () => {
it('should call publish ', () => {
const ipc = new IPC;
sinon.spy(ipc.pub,'publish');
const service = new Service(ipc);
service.sendData();
expect(ipc.pub.publish.calledOnce).to.be.true;
});
});
I found a way to do it just by using sinon
Just need to create a stub instance using sinon.createStubInstance,
then this stub will have all the functionalities from sinon without the implementation of the object (only the class method name)
let ipcStub;
before(() => {
ipcStub = sinon.createStubInstance(IPC)
});
it('should return true', () => {
const ipc = new ipcStub();
const service = new Service(ipc);
service.sendData();
assert.strictEqual(true, ipc.publishSystem.calledOnce);
})
Related
I have a NodeJS + Typescript application with the following class:
export default class OrderStreamWriter {
private readonly redis: IORedis;
private readonly orderStream: string;
private readonly logger: LoggerFactory;
constructor(redisHost: string, redisPort: number, redisPass: string, orderStream: string) {
this.orderStream = orderStream;
this.redis = createRedisClient(redisHost, redisPort, redisPass);
this.logger = new LoggerFactory('streams/OrderStreamWriter');
}
public async write(msg: string): Promise<void> {
await this.redis.xadd(this.orderStream, '*', 'trade', msg).catch((err: Error) => {
this.logger.log(
`Error Writing message to stream (${this.orderStream}): ${err.message}. Quitting...`,
);
process.exit(1);
});
}
}
In another class I use the write method to write the result in a Redis stream.
I want to test that flow without calling the actual write function but just to check that that function will be called with certain parameters, here's my test(run using mocha + sinon):
it('process the input and return an order', () => {
const rule = directOrder[0].rule;
const user = directOrder[0].user;
//const writeStub = sinon.stub(OrderStreamWriter.prototype, "write");
const Writer: any = sinon.stub();
sinon.stub(Writer.prototype, "write");
const writer = new Writer();
const order = {}
// console.log(writeStub)
const directTriggerStrategy: TriggerContext = new TriggerContext(user, rule, writer);
directTriggerStrategy.execute()
sinon.assert.calledWithExactly(writer, order);
})
With both the current code and the commented line const writeStub = sinon.stub(OrderStreamWriter.prototype, "write"); I receive the same error when running the test:
TypeError: Cannot stub non-existent property write
How can I fix this?
// someclass.js
class SomeClass {
prop1;
prop2;
contructor(param1, param2) {
this.prop1 = param1;
this.prop2 = param2;
}
async someFunction(givenParam) {
// do something here
return "somedata";
}
}
// create a factory . This is what you will use to create an instance rather than using the new word ever time to create a new instance.
const someClassInstanceFactory = (param1, param2) => {
return new SomeClass(param1, param2);
};
export default { someClassInstanceFactory, SomeClass };
// ********************************************************
// somemodule.js
// this file uses the class we created above as below
import classAndFactory from "./someclass.js";
const moduleFunction = () => {
const instance = classAndFactory.someClassInstanceFactory(param1, param2);
// this line returns an instance of SomeClass . it's like calling new SomeClass(pram1, param2);
const result = instance.someFunction("givenParam");
console.log(result);
return result;
// result should be 'somedata'
// so how do we unit test moduleFunction and stub instance.someFunction when testing? ?
};
export default moduleFunction;
// *******************************************************************
// somemodule.test.js
import classAndFactory from "./../someclass.js";
import moduleFunction from "./somemodule.js";
const { someClassInstanceFactory, SomeClass } = classAndFactory;
// now if you want to stub any thing what you do is
describe("moduleFunction", () => {
const fakeSomeClassInstance = new SomeClass(1, 2);
// arrange
// stub the factory first to return your fake instance created above.
const expectedResut = "stubbed yoo";
sinon
.stub(classAndFactory, "someClassInstanceFactory")
.returns(fakeSomeClassInstance);
someFunctionStub = sinon
.stub(fakeSomeClassInstance, "someFunction")
.returns(expectedResut);
it("must call someFunction function with required argument", () => {
// act
const result = moduleFunction();
// assert
sinon.assert.calledOnce(someFunctionStub);
assert.equals(result, expectedResut);
});
});
I'm having trouble getting the AWS Secrets Manager module mocked for the jest unit tests... The part it errors on is the .promise(). When I remove that, the code doesn't work for the real Secrets Manager so I think it needs to stay there. How do I mock the getSecretData function so that getSecretData.promise() will work for the mock?
Here is the SecretsManager.js code:
import AWS from 'aws-sdk';
export class SecretsManager {
constructor() {
AWS.config.update({
region: 'us-east-1',
});
this.secretsManager = new AWS.SecretsManager();
}
async getSecretData(secretName) {
try {
const response = await this.secretsManager.getSecretValue({
SecretId: secretName,
}).promise();
const secretString = response.SecretString;
const parsedSecret = JSON.parse(secretString);
return parsedSecret;
} catch (e) {
console.log('Failed to get data from AWS Secrets Manager.');
console.log(e);
throw new Error('Unable to retrieve data.');
}
}
}
Here is the SecretsManager.test.js code:
import { SecretsManager } from '../utils/SecretsManager';
jest.mock('aws-sdk', () => {
return {
config: {
update(val) {
},
},
SecretsManager: function () {
return {
async getSecretValue({
SecretId: secretName
}) {
return {
promise: function () {
return {
UserName: 'test',
Password: 'password',
};
}
};
}
};
}
}
});
describe('SecretsManager.js', () => {
describe('Given I have a valid secret name', () => {
describe('When I send a request for test_creds', () => {
it('Then the correct data is returned.', async () => {
const mockReturnValue = {
UserName: 'test',
Password: 'password',
};
const logger = getLogger();
const secretManager = new SecretsManager();
const result = await secretManager.getSecretData('test_creds');
expect(result).toEqual(mockReturnValue)
});
});
describe('When I send a request without data', () => {
it('Then an error is thrown.', async () => {
const secretManager = new SecretsManager();
await expect(secretManager.getSecretData()).rejects.toThrow();
});
});
});
});
This is the error I get when running the tests:
this.secretsManager.getSecretValue(...).promise is not a function
Any suggestions or pointers are greatly appreciated!
Thank you for looking at my post.
I finally got it to work... figures it'd happen shortly after posting the question, but instead of deleting the post I'll share how I changed the mock to make it work incase it helps anyone else.
Note: This is just the updated mock, the tests are the same as in the question above.
// I added this because it's closer to how AWS returns data for real.
const mockSecretData = {
ARN: 'x',
Name: 'test_creds',
VersionId: 'x',
SecretString: '{"UserName":"test","Password":"password"}',
VersionStages: ['x'],
CreatedDate: 'x'
}
jest.mock('aws-sdk', () => {
return {
config: {
update(val) {
},
},
SecretsManager: function () {
return {
getSecretValue: function ( { SecretId } ) {
{
// Adding function above to getSecretValue: is what made the original ".promise() is not a function" error go away.
if (SecretId === 'test_creds') {
return {
promise: function () {
return mockSecretData;
}
};
} else {
throw new Error('mock error');
}
}
}
};
}
}});
I ran into this issue as well. There may be a more elegant way to handle this that also allows for greater control and assertion, but I haven't found one. Note that the in-test option may work better with newer versions of Jest.
I personally solved this issue by making use of manual mocks and a custom mock file for aws-sdk. In your case, it would look something like the following:
# app_root/__tests__/__mocks__/aws-sdk.js
const exampleResponse = {
ARN: 'x',
Name: 'test_creds',
VersionId: 'x',
SecretString: '{"UserName":"test","Password":"password"}',
VersionStages: ['x'],
CreatedDate: 'x'
};
const mockPromise = jest.fn().mockResolvedValue(exampleResponse);
const getSecretValue = jest.fn().mockReturnValue({ promise: mockPromise });
function SecretsManager() { this.getSecretValue = getSecretValue };
const AWS = { SecretsManager };
module.exports = AWS;
Then in your test file:
// ... imports
jest.mock('aws-sdk');
// ... your tests
So, in a nutshell:
Instead of mocking directly in your test file, you're handing mocking control to a mock file, which Jest knows to look for in the __mocks__ directory.
You create a mock constructor for the SecretsManager in the mock file
SecretsManager returns an instance with the mock function getSecretValue
getSecretValue returns a mock promise
the mock promise returns the exampleResponse
Bada boom, bada bing. You can read more here.
I ran into a same issue, I have tried to solve as below. It worked perfectly in my case.
Terminalsecret.ts
import AWS from 'aws-sdk';
AWS.config.update({
region: "us-east-1",
});
const client = new AWS.SecretsManager();
export class Secret {
constructor(){}
async getSecret(secretName: string) {
let secret: any;
const data = await client.getSecretValue({ SecretId: secretName).promise();
if ('SecretString' in data) {
secret = data.SecretString;
} else {
const buff = Buffer.alloc(data.SecretBinary as any, 'base64');
secret = buff.toString('ascii');
}
const secretParse = JSON.parse(secret);
return secretParse[secretName];
}
}
Terminalsecret.test.ts
import { SecretsManager as fakeSecretsManager } from 'aws-sdk';
import { Secret } from './terminalSecret';
jest.mock('aws-sdk');
const setup = () => {
const mockGetSecretValue = jest.fn();
fakeSecretsManager.prototype.getSecretValue = mockGetSecretValue;
return { mockGetSecretValue };
};
describe('success', () => {
it('should call getSecretValue with the argument', async () => {
const { mockGetSecretValue } = setup();
mockGetSecretValue.mockReturnValueOnce({
promise: async () => ({ SecretString: '{"userName": "go-me"}' })
});
const fakeName = 'userName';
const terminalSecretMock: TerminalSecret = new TerminalSecret()
terminalSecretMock.getTerminalSecret(fakeName);
expect(mockGetSecretValue).toHaveBeenCalledTimes(1);
});
});
In my calendar.spec.js, I have:
const { google } = require('googleapis')
const googleCalendar = google.calendar('v3')
...
before(() => {
sinon.stub(googleCalendar.calendarList, 'list').resolves({ data: true })
})
after(() => {
googleCalendar.calendarList.list.restore()
})
In my calendar.js, I have:
const { google } = require('googleapis')
const googleCalendar = google.calendar('v3')
let { data } = await googleCalendar.calendarList.list({
auth: oauth2Client
})
But it doesn't appear to be stubbed. It goes ahead and tries to connect to Google Calendar. What am I doing wrong?
You can mock the entire googleapis module with mock-require.
const mock = require('mock-require');
mock('googleapis', {
google: {
calendar: () => ({
calendarList: {
list: () => {
return Promise.resolve({
data: {
foo: 'bar'
}
});
}
}
})
}
});
Once you mocked it, your module will consume the mocked module instead of the original so you can test it. So if you module is exposing a method that calls the API, something like that:
exports.init = async () => {
const { google } = require('googleapis');
const googleCalendar = google.calendar('v3');
let { data } = await googleCalendar.calendarList.list({
auth: 'auth'
});
return data;
}
The test will be
describe('test', () => {
it('should call the api and console the output', async () => {
const result = await init();
assert.isTrue(result.foo === 'bar');
});
});
Here is a small repo to play with it: https://github.com/moshfeu/mock-google-apis
Trying to write a unittest for the below module in /utility/sqsThing.js. However I'm having diffuculty mocking the sqs.sendMessage method. Anyone know how I should go about this. I'm using the sinon library, and mocha for running the tests.
The function that I'm trying to unittest utility/sqsThing.js:
const AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const outputQueURL = 'https:awsUrl';
const SQSOutputSender = (results) => {
const params = {
MessageBody: JSON.stringify(results),
QueueUrl: outputQueURL,
};
// Method that I want to mock
sqs.sendMessage(params, function (err, data) {
if (err) {
console.log('Error');
} else {
console.log('Success', data.MessageId);
}
});
};
My attempt at mocking the sqs.sendMessage method in a unittest sqsThingTest.js:
const sqsOutputResultSender = require('../utility/sqsThing');
const AWS = require('aws-sdk');
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const mochaccino = require('mochaccino');
const { expect } = mochaccino;
const sinon = require('sinon');
describe('SQS thing test', function() {
beforeEach(function () {
sinon.stub(sqs, 'sendMessage').callsFake( function() { return 'test' });
});
afterEach(function () {
sqs.sendMessage.restore();
});
it('sqsOutputResultSender.SQSOutputSender', function() {
// Where the mock substitution should occur
const a = sqsOutputResultSender.SQSOutputSender('a');
expect(a).toEqual('test');
})
});
Running this unittest with mocha tests/unit/sqsThingTest.js however I get:
AssertionError: expected undefined to deeply equal 'test'.
info: Error AccessDenied: Access to the resource https://sqs.us-east-1.amazonaws.com/ is denied..
It looks like the mock did not replace the aws api call. Anyone know how I can mock sqs.SendMessage in my test?
You could use rewire js it is a library that lets you inject mocked properties into your module you want to test.
Your require statement would look something like this:
var rewire = require("rewire");
var sqsOutputResultSender = rewire('../utility/sqsThing');
Rewire will allow you to mock everything in the top-level scope of you sqsThing.js file.
Also you need to return the value of sqs.sendMessage this will remove the issue expected undefined to deeply equal 'test'
Your original file would look the same just with a return statement.
//utility/sqsThing.js
const AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const outputQueURL = 'https:awsUrl';
const SQSOutputSender = (results) => {
const params = {
MessageBody: JSON.stringify(results),
QueueUrl: outputQueURL,
};
// Method that I want to mock
return sqs.sendMessage(params, function (err, data) {
if (err) {
console.log('Error');
} else {
console.log('Success', data.MessageId);
}
});
};
You would then write your unit test as follows:
//sqsThingTest.js
var rewire = require("rewire");
var sqsOutputResultSender = rewire('../utility/sqsThing');
const mochaccino = require('mochaccino');
const { expect } = mochaccino;
const sinon = require('sinon');
describe('SQS thing test', function() {
beforeEach(function () {
sqsOutputResultSender.__set__("sqs", {
sendMessage: function() { return 'test' }
});
});
it('sqsOutputResultSender.SQSOutputSender', function() {
// Where the mock substitution should occur
const a = sqsOutputResultSender.SQSOutputSender('a');
expect(a).toEqual('test');
})
});
This example returns an object with a property of sendMessage but this could be replaces with a spy.
Rewire Docs
Try moving the declaration of sqsOutputResultSender after you have stubbed the sendmessage function
var sqsOutputResultSender;
const AWS = require('aws-sdk');
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const mochaccino = require('mochaccino');
const { expect } = mochaccino;
const sinon = require('sinon');
describe('SQS thing test', function() {
beforeEach(function () {
sinon.stub(sqs, 'sendMessage').callsFake( function() { return 'test' });
sqsOutputResultSender = require('../utility/sqsThing');
});
afterEach(function () {
sqs.sendMessage.restore();
});
it('sqsOutputResultSender.SQSOutputSender', function() {
// Where the mock substitution should occur
const a = sqsOutputResultSender.SQSOutputSender('a');
expect(a).toEqual('test');
})
});
Is it possible to test the code below with Jasmine testing tool or any other npm module like rewire or similar?
const AuthValidatorDumb = require('./src/AuthValidatorDumb');
const AuthValidator = require('./src/AuthValidator');
const config = require('../config');
let instance;
if (!instance) {
if (config.get('auth.enabled')) {
instance = AuthValidator;
} else {
instance = AuthValidatorDumb;
}
}
module.exports = instance;
I've got a variant for testing the code above.Suppose you have:
1) The code for index.js in the question above.
2) AuthValidator.js:
class AuthValidator {}
module.exports = AuthValidator;
3) AuthValidatorDumb.js:
class AuthValidatorDumb {}
module.exports = AuthValidatorDumb;
Here is test/index.spec.js:
const proxyquire = require('proxyquire');
const AuthValidator = require('../src/AuthValidator');
const AuthValidatorDumb = require('../src/AuthValidatorDumb');
describe('auth index', () => {
it('should return AuthValidator', () => {
const configMock = { get: () => 'sth' };
const Instance = proxyquire('../index', {
'../config': configMock,
});
expect(new Instance() instanceof AuthValidator).toBeTruthy();
});
it('should return AuthValidatorDumb', () => {
const configMock = { get: () => undefined };
const Instance = proxyquire('../index', {
'../config': configMock,
});
expect(new Instance() instanceof AuthValidatorDumb).toBeTruthy();
});
});