Spying on a function used as a constructutor - javascript

In one of my unit tests I need to spy on a function which is used as a constructor by another function with Sinon library. As per their documentation
...sinon.spy(object, "method") creates a spy that wraps the existing function object.method. The spy will behave exactly like the original method (including when used as a constructor)...
But so far I have failed to make it work even when trying to spy on a constructor called within the test function let alone called by another function.
Unit test:
it('constructor was called.', () => {
const Foo = require('../app/foo');
const fooModule = module.children.find(m => m.id.includes('foo.js'));
const fooSpy = sinon.spy(fooModule, 'exports');
const f = new Foo(5);
expect(fooSpy).calledOnce;
});
Function to be instantiated:
const Foo = function(param) {
console.log('Foo called with: ' + param);
};
Foo.prototype.bar = function(x) {
console.log(`Foo.prototype.bar() called with x: ` + x);
};
module.exports = Foo;
When I step with debugger I can see the function at const fooSpy = sinon.spy(fooModule, 'exports'); being replaced by the spy (has all all sinon properties added like calledOnce and so on...) however when on new Foo(5); it appears that Foo is a non spy object.
I thought this might be a scoping or reference error but I can't seem to find where else Foo would be defined apart from within module.children. It is not on global neither its on window since its running on node.
Currently the test of course fails with:
Foo called with: 5
AssertionError: expected exports to have been called exactly once, but it was called 0 times
at Context.it (test/fooTest.js:18:23)
Thanks in advance for any help!

Your issue is not really with the sinon.spy API, but with how Node modules import functions. When calling sinon.spy, unless we are testing a callback function, we usually need an Object to be the context on which we want to spy on a particular method. This is why your example tries to access the exports Object of the foo.js module. I am not aware of Node giving us access to such an Object.
However, for your example to work, we don't need access to the Foo module's exports, we could simply create a context of our own. For example:
const chai = require("chai");
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
const expect = chai.expect;
chai.use(sinonChai);
describe("foo", function () {
it('constructor was called.', function () {
const context = {
Foo: require("../app/foo"),
};
const fooSpy = sinon.spy(context, "Foo");
new context.Foo(5);
expect(fooSpy).to.be.calledOnceWith(5);
});
});
Of course, the above test is a working solution to the problem provided in your example, but, as a test, it is not very useful because the assertion just verifies the line above it.
Spies are more useful when they are dependencies of the System Under Test (SUT). In other words, if we have some module that is supposed to construct a Foo, we want to make the Foo constructor a Spy so that it may report to our test that the module did indeed call it.
For example, let's say we have fooFactory.js module:
const Foo = require("./foo");
module.exports = {
createFoo(num) {
return new Foo(num);
},
};
Now we would like to create a unit-test that confirms that calling the createFoo function of the fooFactory.js module calls the Foo constructor with the specified argument. We need to override fooFactory.js's Foo dependency with a Spy.
This returns us to our original problem of how can we turn an imported (constructor) Function into a spy when it is not a method on a context Object and so we cannot overwrite it with sinon.spy(context, 'method').
Fortunately, we are not the first ones to encounter this problem. NPM modules exist that allow for overriding dependencies in required modules. Sinon.js provides a How-To on doing this sort of thing and they use a module called proxyquire.
proxyquire will allow us to import the fooFactory.js module into our unit-test, but also (and more importantly) to override the Foo that it depends on. This will allow our unit-test to make fooFactory.js use a sinon.spy in place of the Foo constructor.
The test file becomes:
const chai = require("chai");
const proxyquire = require("proxyquire");
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
const expect = chai.expect;
chai.use(sinonChai);
describe("fooFactory", function () {
it("calls Foo constructor", function () {
const fooSpy = sinon.spy();
const { createFoo } = proxyquire("../app/fooFactory", {
"./foo": fooSpy,
});
createFoo(5);
expect(fooSpy).to.be.calledOnceWith(5);
});
});

Related

global Jest SpyOn function doesen't call the original function

I hope someone might help me understanding the interactivity of js prototypes and jest.spOn().
I have a small Example:
An example class in file TestObj.ts:
export default class TestObj {
foo() {
// Do Something e.g.
console.log("Hello World!");
}
}
The following example Test Case is succeeding but the console.log is never executed.
import TestObj from './TestObj';
const spyObj = jest.spyOn(TestObj.prototype, 'foo');
test('debug test', () => {
const obj = new TestObj();
obj.foo();
expect(spyObj).toHaveBeenCalled();
});
If I change the Example Test Case to the following, the test succeeds and the console.log statement is called as expected.
import TestObj from './TestObj';
test('debug test', () => {
const spyObj = jest.spyOn(TestObj.prototype, 'foo');
const obj = new TestObj();
obj.foo();
expect(spyObj).toHaveBeenCalled();
});
Any idea why the version, using the global spyOn variable does not work as expected?
Edit:
It seems to be not related to prototypes.
The same Issue is there for a function without any kind of class,
editing the First Code snippet (TestObj.ts) to this:
export foo() {
// Do Something e.g.
console.log("Hello World!");
};
We receve the same issue for the updated second snipped. (The test succeeds but the console log is never reached.)
import * as testlib from './TestObj';
const spyObj = jest.spyOn(testlib, 'foo');
test('debug test', () => {
testlib.foo();
expect(spyObj).toHaveBeenCalled();
});
However if we update the second snippet to the following the test succeeds and the console log is executed:
import * as testlib from './TestObj';
const spyObj: jest.SpyInstance;
beforeEach(() => {
spyObj = jest.spyOn(testlib, 'foo');
});
test('debug test', () => {
testlib.foo();
expect(spyObj).toHaveBeenCalled();
});
However I have still no clue why I discover this issue.
who ever comes across this post,
Problem explanation
I did a lot more of research(try&error, mdn, jest manpage and a lot of medium articles) and I guess that I found out the reason for the strange behavior. To understand this issue it is important to know a number of points:
1: JS prototypes are global variables, every object of the corresponding type relies on.
2: Jest does not reset global variables after every test, changes made before, or inside any test to a global variable will be kept for the whole test suite (file).
3: The jest spy on function is actually a mockup of the specified function with an implementation calling the function it self. e.g.:
jest.SpyOn(TestObj.prototype, 'foo'); actually is implemented as: TestObj.prototype.foo = new jest.fn().mockImplementation(()=>{original_TestObj.prototype.foo()});
This means spying on a function of a class prototype is actually changing a global variable.
4: Depending on your jest config, there is the possibility to reset mockup functions before every test to the default value. But be careful the default function for spyOn seems to be the same as for jest.fn() it self, an empty implementation, which means the mock-up is still callable but no code, especially not the original implementation is executed.
Solution
avoid changing global variables, if you want your testcases independent from each other.
avoid spying on prototypes, in test cases, if you require the spy only in a single test try to spy on the local object eg:
test('should foo', () => {
const testObj = new TestObj();
const spyOnFn = jest.spyOn(testObj, 'foo');
// Do anything
expect(spyOnFn).to//Have been anything
});
if requiring spyOn implementations of the same function
in more than one test try to create a global variable for the Test
but use the before each functionality of jest to setup the Spy. This
functionality is executed after the reset of all mocks (if enabled).
e.g.:
let spyOnFunction1: jest.SpyInstance;
beforeEach(()=> {
spyOnFunction1 = jest.spyOn(TestObj.prototype, 'foo');
});

How to get/set out-of-scope variables when testing a function with dependency on these (jest)?

I'm trying to have full test coverage on some helper functions in my project. My first tests for either of the functions checks whether 2 out-of-scope variables are declared or not, which run successfully.
What I want to do is, jest to set/mock wrapper and layer variables declared out-of-scope of the functions to be tested, but can't seem to have it right no matter how hard I tried.
Thankful and appreciate any kind of help in this direction as I'm fairly rookie in terms of writing tests in general not to mention tests for jest.
I have tried jest.mock by mocking by default my imported module and then did jest.requireActual on 2 functions.
I have tried jest.doMock with a module factory which sets these 2 variables and then returns in an object.
Module: helpers/index.js
let wrapper; // Is created on runtime ergo initially undefined.
const layer = document.createElement('div');
layer.classList.add('layer');
const isElement = elm => {
if (!(elm instanceof Element)) {
throw new Error('Given element is not of type Element or is undefined!');
}
}
export const someFn = async () => {
wrapper = document.querySelector('.wrapper');
isElement(wrapper);
isElement(layer);
// Code that needs to be tested
// but cannot since when testing this function
// 'wrapper' and 'layer' are always undefined.
}
Test: helpers/index.test.js
// Helpers
import {someFn} from 'helpers';
describe('Method: someFn', () => {
test('should NOT throw', async () => {
await expect(someFn()).resolves.not.toThrow(); // but it does!
});
});
Actual result: Received promise rejected instead of resolved
Expected result: Not to throw, considering these out-of-scope variables that this function relies on, can somehow be set/mocked while testing it.

Stub/spy on singleton with Sinon

I have a need to write some unit tests where I check on calls to singleton functions. Basically, if I have this:
const somePackage = require('some-package');
And am calling it with:
somePackage();
I want to be able to spy/stub on this singleton call to prove that it was called. I know how to do this with methods using sinon.stub(somePackage, 'someMethod') but not for just a singleton.
Right now I actually have to write integration tests by executing the code when in reality I want to write unit tests and show that these external methods were called. I'm trusting that the developer of those packages did their own testing for the functionality.
You can spy on any function with sinon:
const spy = sinon.spy(myFunc);
To stub a single function you can use proxyquire as described in this issue:
const proxyquire = require('proxyquire')
const sinon = require('sinon')
const sum = sinon.stub()
const ModuleWithDependency = proxyquire('module', {
'sum': sum
})

How to sinon spy module export utility functions

In javascript (ES6), I have a utility module which just contains some functions, and then in the end of the file, I export them like so:
module.exports = {
someFunction1,
someFunction2,
someFunction3,
}
Then I want to write unit tests for those functions. Some of the functions depend on each other; they call each other in a way, that for example, someFunction1 might call someFunction2. There's no circular problems.
Everything works well, until I need to spy that one of the functions is called. How can I do it? Currently I'm using Chai and Sinon.
In the test file, I have imported the whole file as a module:
const wholeModule = require('path/to/the/js/file')
And finally, my test looks like following:
it('should call the someFunction2', (done) => {
const spy = sinon.spy(wholeModule, 'someFunction2')
wholeModule.someFunction1() // someFunction2 is called inside someFunction1
assert(spy.calledOnce, 'someFunction2 should be called once')
done()
})
The problem is, that the test fails, because in someFunction1, the someFunction2 function is used directly. I apply the spy to the module object's function. But that's a different object. Here's an example of the someFunction1:
function someFunction1() {
someFunction2()
return 2;
}
I know the reason why it won't work, but I don't know what would be the best practise in this case to make it work? Please help!
You can use rewire module. Here is an example:
Source code:
function someFunction1() {
console.log('someFunction1 called')
someFunction2();
}
function someFunction2() {
console.log('someFunction2 called')
}
module.exports = {
someFunction1: someFunction1,
someFunction2: someFunction2
}
Test case:
'use strict';
var expect = require('chai').expect;
var rewire = require('rewire');
var sinon = require('sinon');
var funcs = rewire('../lib/someFunctions');
it('should call the someFunction2', () => {
var someFunction2Stub = sinon.stub();
funcs.__set__({
someFunction2: someFunction2Stub,
});
someFunction2Stub.returns(null);
funcs.someFunction1();
expect(someFunction2Stub.calledOnce).to.equal(true);
});
As you are already aware this happens because you are stubbing the exported reference not the actual method in the module. It works in any module that includes the one you are replacing since they include the exported references but when inside the same module it's just calling the local function.
The easiest solution I've found is just to call the reference:
function someFunction1() {
this.someFunction2()
return 2;
}

Stub out module function

Edit: Being a little bit more precise.
I want to test usecases for a Github API wrapper extension, that our team has created. For testing, we don't want to use API wrapper extension directly, so we want to stub out its functions. All calls to the API wrapper should be stubbed out for the tests, not just creating a clone stub.
I have a module "github" in Node.js:
module.exports = function(args, done) {
...
}
And I am requiring it like this:
var github = require('../services/github');
Now, I would like to stub out github(...) using Sinon.js:
var stub_github = sinon.stub(???, "github", function (args, callback) {
console.log("the github(...) call was stubbed out!!");
});
But sinon.stub(...) expects from me to pass an object and a method and does not allow me to stub out a module that is a function.
Any ideas?
There might be a way to accomplish this in pure Sinon, but I suspect it would be pretty hacky. However, proxyquire is a node library that is designed for solving these sort of issues.
Supposing you want to test some module foo that makes use of the github module; you'd write something like:
var proxyquire = require("proxyquire");
var foo = proxyquire(".foo", {"./github", myFakeGithubStub});
where myFakeGithubStub can be anything; a complete stub, or the actual implementation with a few tweaks, etc.
If, in the above example, myFakeGithubStub has a property "#global" set as true, (i.e. by executing myFakeGithubStub["#global"] = true) then the github module will be replaced with the stub not only in the foo module itself, but in any module that the foo module requires. However, as stated in the proxyquire documentation on the global option, generally speaking this feature is a sign of poorly designed unit tests and should be avoided.
I found that this worked for me...
const sinon = require( 'sinon' );
const moduleFunction = require( 'moduleFunction' );
// Required modules get added require.cache.
// The property name of the object containing the module in require.cache is
// the fully qualified path of the module e.g. '/Users/Bill/project/node_modules/moduleFunction/index.js'
// You can get the fully qualified path of a module from require.resolve
// The reference to the module itself is the exports property
const stubbedModule = sinon.stub( require.cache[ require.resolve( 'moduleFunction' ) ], 'exports', () => {
// this function will replace the module
return 'I\'m stubbed!';
});
// sidenote - stubbedModule.default references the original module...
You have to make sure that you stub the module (as above) before it's required elsewhere...
// elsewhere...
const moduleFunction = require( 'moduleFunction' );
moduleFunction(); // returns 'I'm stubbed!'
Simplest solution is to refactor your module:
instead of this:
module.exports = function(args, done) {
...
}
do this:
module.exports = function(){
return module.exports.github.apply(this, arguments);
};
module.exports.github = github;
function github(args, done) {
...
}
Now you can require it with:
const github = require('../services/github.js');
//or
const github = require('../services/github.js').github;
To stub:
const github = require('../services/github.js');
let githubStub = sinon.stub(github, 'github', function () {
...
});
If you are doing
var github = require('../services/github');
in global scope, then you can using 'global' as the object and 'github' as the method to be stubbed out.
var stub_github = sinon.stub(global, "github", function (args, callback) {
console.log("the github(...) call was stubbed out!!");
});

Categories

Resources