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);
});
});
Related
I'm following a tutorial which is using jest to test the javascript. The instructor created a static function called genesis() on a class called Block and it worked for him just fine, but when I tried to do it I got TypeError: block.genesis is not a function. If I remove the static keyword it recognises the function and the test passes.
Here is the class:
const { GENESIS_DATA } = require('./config');
class Block {
constructor({ timestamp, lastHash, hash, data }) {
this.timestamp = timestamp;
this.lastHash = lastHash;
this.hash = hash;
this.data = data;
}
static genesis() {
return new Block(GENESIS_DATA);
}
}
module.exports = Block;
And the test:
const Block = require('./block');
const { GENESIS_DATA } = require('./config');
describe('Block', () => {
const timestamp = 'a-date';
const lastHash = 'a-hash';
const hash = 'another-hash';
const data = ['blockchain', 'data'];
const block = new Block({ timestamp, lastHash, hash, data });
describe('genesis()', () => {
const genesisBlock = block.genesis();
it('returns a block instance', () => {
expect(genesisBlock instanceof Block).toBe(true);
});
it('returns the genesis data', () => {
expect(genesisBlock).toEqual(GENESIS_DATA);
});
});
});
The genesis method is part of the class, not the instance. You want to call Block.genesis() instead of block.genesis()
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();
});
});
I have a DAO class as a separate layer for getting data from my repository. I made the class Singleton and methods static.
In another class I made other service methods for transforming the data. I would like to write tests for this code but don't succeed.
How to mock the Dao repository methods?
This is what I tried so far:
// error: TS2345: Argument of type "getAllPosts" is not assignable to paramenter of type "prototype" | "getInstance"
const dao = sinon.stub(Dao, "getAllPosts");
// TypeError: Attempted to wrap undefined property getAllPosts as function
const instance = sinon.mock(Dao);
instance.expects("getAllPosts").returns(data);
export class Dao {
private noPostFound: string = "No post found with id";
private dbSaveError: string = "Error saving to database";
public static getInstance(): Dao {
if (!Dao.instance) {
Dao.instance = new Dao();
}
return Dao.instance;
}
private static instance: Dao;
private id: number;
private posts: Post[];
private constructor() {
this.posts = posts;
this.id = this.posts.length;
}
public getPostById = (id: number): Post => {
const post: Post = this.posts.find((post: Post) => {
return post.id === id;
});
if (!post) {
throw new Error(`${this.noPostFound} ${id}`);
}
else {
return post;
}
}
public getAllPosts = (): Post[] => {
return this.posts;
}
public savePost = (post: Post): void => {
post.id = this.getId();
try {
this.posts.push(post);
}
catch(e) {
throw new Error(this.dbSaveError);
}
}
}
Solved it like this:
// create an instance of Singleton
const instance = Dao.getInstance();
// mock the instance
const mock = sinon.mock(instance);
// mock "getAllPosts" method
mock.expects("getAllPosts").returns(data);
I have created a method that grabs the image data based on another method. As you can see the uploadAvatar() method stores the object back from Cloudinary into a constructor object this._userImage;. Then my getUserImage() method should return the user image data but instead it returns {} that when I log it to the console :P.
ImageUploader.js
'use strict';
const cloudinary = require('cloudinary');
class ImageUploader {
constructor(imageObj) {
this.imageObj = imageObj;
this._apiKey = "key";
this._apiSecret = "secret";
this.config = cloudinary.config({
cloud_name: 'name',
api_key: this._apiKey,
api_secret: this._apiSecret
});
this._userImage = {}; // where user image object should be stored
}
* uploadAvatar(path) {
cloudinary.uploader.upload(path, (data) => {
this._userImage = data; // where I overwrite the constructor obj
});
}
getUserImage() {
return this._userImage; // this returns `{}` when I log it in UsersController.js
}
}
module.exports = ImageUploader;
UsersController.js
'use strict'
const Validator = require("../Helpers/ValidatorHelper.js");
const ImageUploader = require("../Helpers/ImageUploader.js");
class UsersController {
* registerView (request, response) {
yield response.sendView('users.register');
}
* register (request, response) {
const user = request.only('display_name', 'username', 'email', 'password', 'confirm_password', 'console');
var avatar = request.file('avatar');
let validator = new Validator(user, avatar);
let imageUpload = new ImageUploader(avatar);
let avatarStatus = yield validator.validateAvatar();
var img;
if (avatarStatus) { // is true
yield imageUpload.uploadAvatar(avatar.file.path);
} else { // is false
// pick a random avatar
}
console.log(imageUpload.getUserImage());
return response.status(200).json({
user: user,
avatar: imageUpload.getUserImage()
});
}
}
module.exports = UsersController
Not sure but here you could lost your this context.
cloudinary.uploader.upload(path, (data) => {
this._userImage = data; // where I overwrite the constructor obj
});
Check if 'this' there still links to your UsersController object
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);
})