I'm having some issues using sinon stubs and it may stem from how I'm implementing namespacing on the module that I'm looking to stub. Methods directly defined on the prototype are stubbed as I would expect.
...my module.js
const Constructor = require('./constructor') //...just exports a singleton
/* Need to namespace some of my functions and retain the `this` context */
Object.defineProperty(Constructor.prototype, 'es', {
get: function() {
return {
method: require('./implementations/doesSomething.js').bind(this)
}
}
});
module.exports = Constructor;
/* ...testFile.js */
const Constructor = require('./constructor');
const instance = new Constructor();
const sinon = require('sinon');
sinon.stub(instance.es, 'method', function() {
return 'hijacked original method'
});
As mentioned on the Sinon issue tracker, the problem here is that using a plain Object.defineProperty(obj, 'prop') call does something else than plainly creating it using assignment (obj['prop'] = ...).
Generally, if you try defining your property without Object.defineProperty it will be stubbable, but using defineProperty (without creating a special configuration) will make it impossible to stub the property. The reason is simply that the default values for writeable and configurable are false! You cannot delete them or change them. And if you can't do that, then Sinon won't help you. So, generally, you need to add writeable: true, configurable: true in your property definition.
Now there was one more thing I forgot to answer originally:
You are not trying to wrap a function on Constructor.prototype.es.method - what you are trying to wrap is the function on the object returned by the getter on the property for es. That will never work. Why? Simply because the returned object is never the same. You are creating a new object around method each time. If you really need to replace/stub the method property, you actually need to replace the entire Constructor.prototype.es property instead. If you need this namespacing, you can vastly simplify this, and also enable stubbing, like this:
Constructor.prototype.es = {};
Object.defineProperty(Constructor.prototype.es, 'method', {
get: function() {
return someFunction.bind(this);
},
writeable: true,
configurable:true
}
An expanded, fully working example (Gist for download):
// constructor.js
const someFunction = function(){
return this.value;
}
function Constructor(){ };
Constructor.prototype.es = { value : 100 };
Object.defineProperty(Constructor.prototype.es, 'method', {
get: function() {
return someFunction.bind(this);
},
writeable: true,
configurable:true
});
// test.js
const instance = new Constructor();
console.log(instance.es.method()) // => 100
// using this won't work:
// sinon.stub(instance.__proto__.es, 'method').returns(42);
// because the getter is returning a _new_ function each time
// therefore you need to attack the actual getter function:
const stub = sinon.stub(instance.__proto__.es, 'method').value(()=>42);
console.log(instance.es.method()) // => 42
stub.get(()=>()=>84);
console.log(instance.es.method()) // => 84
stub.restore();
console.log(instance.es.method()) // => 100
// the above is working on the prototype, can't we do this on the instance?
// yes, we can, but remember that the `es` object is shared, so we
// can avoid modifying it by shadowing it further down the prototype
instance.es = { method: sinon.stub().returns(256) };
console.log(instance.es.method()) // => 256
delete instance.es
console.log(instance.es.method()) // => 100
<script src="https://unpkg.com/sinon#2.3.5/pkg/sinon.js"></script>
Related
I have a component class as below which create the rest and websocket connections using a third party npm module. I could change the Component.constructor to accept the module as a dependency so that I can inject a mock version during my Jest testing. But I read about Mocks with Jest, I thought I want to try it, but I cannot seem to understand how to intercept Api.Rest() and Api.Websocket return values.
// component.ts
import * as Api from 'npm-module'
import * as wait from 'wait-for-stuff' // actual npm module
export class Component {
private _rest:any;
private _websocket:any;
public events = new EventEmitter();
constructor() {
// I want to intecept the return value of
// Api.Rest() and Api.Websocket() to use mock versions.
this._rest = new Api.Rest();
this._websocket = new Api.Websocket();
this._init();
}
private _init() {
// so that when do stuff with this._rest and this._websocket;
// I can control what is the expected results during test
this._websocket.onUpdate((data) => {
events.emit('update', data);
});
var value = wait.for.promise(this._rest.getSomething());
}
}
Do I have to use another test library like Sinon or Jasmine?
Here is a simplified working example to get you started:
// #ts-ignore
import * as Api from 'npm-module'; // <= (ts-ignore since "npm-module" doesn't exist)
import EventEmitter from 'events';
jest.mock('npm-module', () => {
const getSomethingMock = jest.fn(); // <= always return...
const onUpdateMock = jest.fn(); // <= ...the same mocks...
return {
Rest: () => ({ getSomething: getSomethingMock }),
Websocket: () => ({ onUpdate: onUpdateMock })
}
},
{ virtual: true }); // <= (use virtual since "npm-module" doesn't exist)
class Component {
private _rest: any;
private _websocket: any;
public events = new EventEmitter();
constructor() {
this._rest = new Api.Rest();
this._websocket = new Api.Websocket();
this._init();
}
private _init() {
this._websocket.onUpdate((data) => { // <= ...so that this onUpdate...
this.events.emit('update', data);
});
}
}
test('Component', () => {
const component = new Component();
const listener = jest.fn();
component.events.on('update', listener);
const onUpdate = new Api.Websocket().onUpdate; // <= ...is the same as this one
const onUpdateArrowFunction = onUpdate.mock.calls[0][0]; // <= get the arrow function passed to it
onUpdateArrowFunction('mock data'); // <= now call the function
expect(listener).toHaveBeenCalledWith('mock data'); // Success!
});
Details
Jest takes over the require system and allows you to specify what you want it to return when a module is required (note that TypeScript import statements get compiled to require calls).
One way to mock a module is to create a manual mock by creating a file at __mocks__/npm-module.ts that contains your mock.
Another way (show above) is to use jest.mock and pass it a module factory function.
Whenever the module is required during the test Jest will return the mocked module instead.
Note that the example above always returns the same mock for getSomething and onUpdate so those mock functions can be retrieved during the test.
Also note the use of mockFn.mock.calls to retrieve this arrow function:
(data) => {
this.events.emit('update', data);
}
...which gets passed to onUpdate. Once it has been retrieved, it can be called directly which triggers the listener as expected.
I have a sinon stub on a prototype and method...
stub = sinon.stub(MyType.prototype, 'someFunction');
MyType has some value in a property depending on which instance it is. Lets call the property identifier.
I need to check two things...
That someFunction was called with the correct parameters.
expect(stub).to.have.been.calledWith('Some Parameter'); (This works as expected).
That the identifier of the instance the function was called on is correct. There are many instances of MyType and I need to check the function was called on the correct one.
I can do the first check. But I don't know how to (or even if) I can do the second check.
Is this possible?
Thanks
Yes, this is possible.
You can use sinon.assert.calledOn, spy.calledOn, spyCall.thisValue, or spy.thisValues to check the this value for calls:
import * as sinon from 'sinon';
class MyType {
constructor(id) {
this.identifier = id;
}
someFunction(arg) { }
}
test('someFunction', () => {
const stub = sinon.stub(MyType.prototype, 'someFunction');
const one = new MyType("oneId");
const two = new MyType("twoId");
one.someFunction('firstArg');
two.someFunction('secondArg');
sinon.assert.calledWith(stub.firstCall, 'firstArg'); // SUCCESS
sinon.assert.calledOn(stub.firstCall, one); // SUCCESS
expect(stub.firstCall.thisValue.identifier).to.equal('oneId'); // SUCCESS
sinon.assert.calledWith(stub.secondCall, 'secondArg'); // SUCCESS
sinon.assert.calledOn(stub.secondCall, two); // SUCCESS
expect(stub.secondCall.thisValue.identifier).to.equal('twoId'); // SUCCESS
});
import ManagerDaoStub from '../salesforce/__test__/ManagerDaoStub';
import criticalMerchants from '../criticalMerchants';
describe('criticalMerchants Unit Tests', () => {
before(() => {
ManagerDaoStub.initStubs();
});
after(() => {
ManagerDaoStub.restoreStubs();
});
it('assert the arguments of stubbed method', (done)=>{
let load = criticalMerchants.createCases(MERCHANT, DEVICE_ID, KEY, {});
return done();
});
})
This is the test file written in node criticalMerchants.test.js. The method i want to test which is createCases uses a method in ManagerDao, which has been stubbed in ManagerDaoStub as below.
import ManagerDao from '../ManagerDao';
class ManagerDaoStub {
constructor() {
this.sandbox = sinon.sandbox.create();
}
initStubs(sandbox) {
this.sandbox = sandbox || this.sandbox;
this.restoreStubs();
this.initFindOpenCases();
}
restoreStubs() {
this.sandbox.restore();
}
initFindOpenCases() {
let findOpenCases = this.sandbox.stub(ManagerDao, "findOpenCases");
findOpenCases
.withArgs(DEVICE_ID, KEY, match.func)
.callsArgWith(2, new Error("Test error"));
}
}
I want to assert whether this stubbed method initFindOpenCases was called with the right arguments (DEVICE_ID,KEY,null). I used
sinon.assert.calledWith(ManagerDaoStub.initFindOpenCases, DEVICE_ID, KEY, null) and this gives the following error:
AssertError: initFindOpenCases() is not stubbed.
Can someone suggest a proper way to do this?
First off, if ManagerDao.initFindOpenCases is an instance method (I'm unsure since you haven't shared its definition), then you can't stub it on the constructor like you've done here:
let findOpenCases = this.sandbox.stub(ManagerDao, "findOpenCases")
You need to either create an instance first-- then stub it on that instance-- or stub it on the prototype itself like so:
let findOpenCases = this.sandbox.stub(ManagerDao.prototype, "findOpenCases");
Secondly, you're making the same mistake again in your assertion, combined with another:
sinon.assert.calledWith(ManagerDaoStub.initFindOpenCases, DEVICE_ID, KEY, null)
ManagerDaoStub is the constructor, and it does not have an initFindOpenCases property. Its prototype does, and thus its instances do as well. On top of that, ManagerDaoStub.prototype.initFindOpenCases is still not a stub. It's a method you're calling to create a stub, but it is not itself a stub. More plainly, you're getting ManagerDao mixed up with ManagerDaoStub.
Assuming you make example change above, you can make your assertion work like this:
sinon.assert.calledWith(ManagerDao.prototype.initFindOpenCases, DEVICE_ID, KEY, null)
However, this isn't all I would recommend changing. This latter mixup is arising largely because you're vastly over-complicating the setup code of your test. You don't need to make an entire class to stub one method of ManagerDao.
Instead, just replace your before and after calls with these:
beforeEach(() => {
// Create the stub on the prototype.
sinon.stub(ManagerDao.prototype, 'findOpenCases')
.withArgs(DEVICE_ID, KEY, sinon.match.func)
.callsArgWith(2, newError('Test Error'));
});
afterEach(() => {
// As of sinon 5.0.0, the sinon object *is* a sandbox, so you can
// easily restore every fake you've made like so:
sinon.restore();
});
Aside from that, I recommend looking deeply into the difference between properties on a constructor and properties on its prototype. That knowledge will make stuff like this much easier for you. Best place to start is probably here on MDN.
I am hoping to clear up some of my confusion pertaining to arrow functions and lexical this, my use case with mongoose.
When adding a method to a mongoose Schema, one cannot use an arrow function.
According to this article: https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4
"Lexical Scoping just means that it uses this from the code that contains the Arrow Function."
So if I use an arrow function in a mongoose method, why does 'this' not refer to the schema object, whereas a pre-es6 function does? If the schema and arrow function are in the same file, is the lexical scope not bound to the schema?
Thank you!
UserSchema.methods.toJSON = function() {
const user = this;
const userObject = user.toObject();
return _.pick(userObject, ['_id', 'email']);
};
Lexical Scoping just means that it uses this from the code that contains the Arrow Function.
I'll just demonstrate it:
window.answer = 'Unknown'; // `this` equals to `window` in browser (no strict mode)
const object = {
answer: 42,
arrow: () => this.answer,
wrap() {
const arrow = () => this.answer;
return arrow();
},
stillOuter() { return this.arrow();},
method() {return this.answer;},
likeArrow: function() {return this.answer;}.bind(this)
};
console.log(object.arrow(), object.stillOuter(), object.likeArrow()); // Unknown Unknown
console.log(object.method(), object.wrap()); // 42 42
Arrow function's this just belongs to outer context.
So, if your arrow functions will be declared inside of correct object, this will be correct(almost) too.
Look into that workaround:
let tmp = Symbol(); // just to not interfere with something
UserSchema.methods[tmp] = function() {
this.toJson = data => JSON.stringify(data);
// All arrow functions here point into `UserSchema.methods` object
// It will be still `UserSchema.methods` if implementation will copy these methods into other objects or call in the other context
};
UserSchema.methods[tmp]();
delete UserSchema.methods[tmp];
sir/madam exlain the flow of node.js from client to server with the dynamic parameters passing from userinterface to api's based up on these parameters we will get the output from api.for example sabre api etc..
exports.flightDestinations = function(req, res) {
var callback = function(error, data) {
if (error) {
// Your error handling here
console.log(error);
} else {
// Your success handling here
// console.log(JSON.parse(data));
res.send(JSON.parse(data));
}
};
sabre_dev_studio_flight.airports_top_destinations_lookup({
topdestinations: '50'
}, callback);
};
we want this value 50 from user...and how to give this value?and how to call this function in node.js.
The exports variable is initially set to that same object (i.e. it's a shorthand "alias"), so in the module code you would usually write something like this:
var myFunc1 = function() { ... };
var myFunc2 = function() { ... };
exports.myFunc1 = myFunc1;
exports.myFunc2 = myFunc2;
to export (or "expose") the internally scoped functions myFunc1 and myFunc2.
And in the calling code you would use:
var m = require('mymodule');
m.myFunc1();
where the last line shows how the result of require is (usually) just a plain object whose properties may be accessed.
NB: if you overwrite exports then it will no longer refer to module.exports. So if you wish to assign a new object (or a function reference) to exports then you should also assign that new object to module.exports
It's worth noting that the name added to the exports object does not have to be the same as the module's internally scoped name for the value that you're adding, so you could have:
var myVeryLongInternalName = function() { ... };
exports.shortName = myVeryLongInternalName;
// add other objects, functions, as required
followed by:
var m = require('mymodule');
m.shortName(); // invokes module.myVeryLongInternalName