I'm having trouble spying on a function that is imported from a node module. I am testing a child class and the module is imported in a grandparent class, and I need to see what arguments the function is called with.
The code works as expected but I've tested with Jasmine's built in spyOn
and also sinon.spy
but neither spy is being called.
Code:
// ChildClass.js
const ParentClass = require('./ParentClass');
module.exports = class ChildClass extends ParentClass {
constructor () {
super();
this.foo();
}
foo () {
this.message = this.importGetter('Some input');
}
};
// ParentClass.js
const GrandparentClass = require('./GrandparentClass');
module.exports = class ParentClass extends GrandparentClass {
constructor () {
super();
this._message = null;
}
get message () {
return this._message;
}
set message (value) {
this._message = value;
}
};
// GrandparentClass.js
const nodeModuleFunction = require('./nodeModule').nodeModuleFunction;
module.exports = class GrandparentClass {
get importGetter () {
return nodeModuleFunction;
}
};
// nodeModule.js
async function nodeModuleFunction (input) {
console.error('Do something with input: ', input);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Returned from node module.');
}, 300);
});
}
exports.nodeModuleFunction = nodeModuleFunction;
Test code:
// test.spec.js
const ChildClass = require('./ChildClass');
const nodeModule = require('./nodeModule');
const sinon = require('sinon');
describe('ChildClass test', () => {
describe('Jasmine spy', () => {
it('should call nodeModule.nodeModuleFunction with given value', done => {
spyOn(nodeModule, 'nodeModuleFunction').and.callThrough();
const object = new ChildClass();
expect(object.message).not.toBeNull();
expect(nodeModule.nodeModuleFunction).toHaveBeenCalled();
expect(nodeModule.nodeModuleFunction).toHaveBeenCalledWith('Some input');
object.message.then(message => {
expect(message).toBe('Returned from node module.');
done();
});
});
});
describe('Sinon spy', () => {
it('should call nodeModule.nodeModuleFunction with given value', done => {
const spy = sinon.spy(nodeModule, 'nodeModuleFunction');
const object = new ChildClass();
expect(object.message).not.toBeNull();
expect(spy.called).toBe(true);
expect(spy.withArgs('Some input').calledOnce).toBe(true);
object.message.then(message => {
expect(message).toBe('Returned from node module.');
done();
});
});
});
});
Test results:
Jasmine started
Do something with input: Some input
ChildClass test
Jasmine spy
✗ should call nodeModule.nodeModuleFunction with given value
- Expected spy nodeModuleFunction to have been called.
- Expected spy nodeModuleFunction to have been called with [ 'Some input' ] but it was never called.
Do something with input: Some input
Sinon spy
✗ should call nodeModule.nodeModuleFunction with given value
- Expected false to be true.
- Expected false to be true.
Editing with solution following Brian's suggestion:
const nodeModule = require('./nodeModule');
describe('ChildClass test', () => {
let ChildClass;
beforeAll(() => {
spyOn(nodeModule, 'nodeModuleFunction').and.callThrough(); // create the spy...
ChildClass = require('./ChildClass'); // ...and now require ChildClass
});
afterEach(() => {
nodeModule.nodeModuleFunction.calls.reset();
});
describe('Jasmine spy', () => {
it('should call nodeModule.nodeModuleFunction with given value', done => {
const object = new ChildClass();
expect(object.message).not.toBeNull();
expect(nodeModule.nodeModuleFunction).toHaveBeenCalled();
expect(nodeModule.nodeModuleFunction).toHaveBeenCalledWith('Some input');
object.message.then(message => {
expect(message).toBe('Returned from node module.');
done();
});
});
it('should still call nodeModule.nodeModuleFunction with given value', done => {
const object = new ChildClass();
expect(object.message).not.toBeNull();
expect(nodeModule.nodeModuleFunction).toHaveBeenCalled();
expect(nodeModule.nodeModuleFunction).toHaveBeenCalledWith('Some input');
object.message.then(message => {
expect(message).toBe('Returned from node module.');
done();
});
});
});
});
const nodeModule = require('./nodeModule');
const sinon = require('sinon');
describe('ChildClass test', () => {
let spy;
let ChildClass;
beforeAll(() => {
spy = sinon.spy(nodeModule, 'nodeModuleFunction'); // create the spy...
ChildClass = require('./ChildClass'); // ...and now require ChildClass
});
afterEach(() => {
spy.resetHistory();
});
afterAll(() => {
spy.restore();
});
describe('Sinon spy', () => {
it('should call nodeModule.nodeModuleFunction with given value', done => {
const object = new ChildClass();
expect(object.message).not.toBeNull();
expect(spy.called).toBe(true);
expect(spy.withArgs('Some input').calledOnce).toBe(true);
object.message.then(message => {
expect(message).toBe('Returned from node module.');
done();
});
});
it('should still call nodeModule.nodeModuleFunction with given value', done => {
const object = new ChildClass();
expect(object.message).not.toBeNull();
expect(spy.called).toBe(true);
expect(spy.withArgs('Some input').calledOnce).toBe(true);
object.message.then(message => {
expect(message).toBe('Returned from node module.');
done();
});
});
});
});
GrandparentClass.js
requires nodeModule.js
and grabs a reference to nodeModuleFunction
as soon as it runs ...
...so you just need to make sure your spy is in place before it runs:
const nodeModule = require('./nodeModule');
const sinon = require('sinon');
describe('ChildClass test', () => {
describe('Sinon spy', () => {
it('should call nodeModule.nodeModuleFunction with given value', done => {
const spy = sinon.spy(nodeModule, 'nodeModuleFunction'); // create the spy...
const ChildClass = require('./ChildClass'); // ...and now require ChildClass
const object = new ChildClass();
expect(object.message).not.toBeNull(); // Success!
expect(spy.called).toBe(true); // Success!
expect(spy.withArgs('Some input').calledOnce).toBe(true); // Success!
object.message.then(message => {
expect(message).toBe('Returned from node module.'); // Success!
done();
});
});
});
});
I updated the Sinon
test, but the approach works for the Jasmine
test as well.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.