global Jest SpyOn function doesen't call the original function - javascript

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');
});

Related

It seems that the beforeEach hook is executing for another test class (Mocha testing framework)

I am not sure what is happening.
I am using the Mocha testing framework to learn writing tests for solidity classes.
I met a strange behaviour, that I never met before. I have some previous experience with Mocha (with Cypress), but never saw this strange behaviour.
What is the problem:
I have two testing classes.
The first class:
//01. Call library modules.
const assert = require('assert'); // Declare an assertion library module.
const { beforeEach } = require('mocha');
//02. Create an example class with some logic inside.
class exampleClass {
func1() {
return 'random value 1';
}
func2() {
return 'random_value_2';
}
}
//03. Declare global variables.
let exam;
//04. Declare the "beforeEach" function. This function will be executed before every "it" test.
beforeEach(() => {
exam = new exampleClass(); // Declare a constructor for exampleClass.
});
//05. Create a test suite using "describe" function.
describe('name of the describe', () => {
//06. Add tests using "it" function.
it('This example shows how the assert will pass', () => {
assert.equal(exam.func1(), 'random value 1'); // Make an assertion to verify that the two values are equal.
});
xit('This example shows how the assert will fail, but the test will be skipped because the "X" symbol is added like a prefix for "it".', () => {
assert.equal(exam.func1(), 'random value'); // Make an assertion to verify that the two values are equal.
});
it('This example shows how the assert will pass', () => {
assert.equal(exam.func2(), 'random_value_2'); // Make an assertion to verify that the two values are equal.
});
});
The first class is a basic example of using the Mocha testing framework.
The second class:
//01. Call library modules.
const ganache = require('ganache'); // Declare a ganache library module.
const Web3 = require('web3'); // Declare a web3 library module.
//02. Create an instances.
const web3 = new Web3(ganache.provider()); // Declare an instance for Web3 including ganache.
//03. Declare the "beforeEach" function. This function will be executed before every "it" test.
beforeEach(() => {
// Get a list of all accounts.
web3.eth.getAccounts()
.then(fetchedAccounts => { // Get an access to all of the accounts. We use then, because "getAccounts()" function is returning a promise.
console.log(fetchedAccounts);
});
});
//04. Create a test suite using "describe" function.
describe('Generate fake eth accounts example 1', () => {
//05. Add tests using "it" function.
it('A0.1. example 1', () => {
});
});
The second class is an example of the generation of fake data (fake eth accounts).
If I execute the classes separately - everything is working as expected.
But if I execute all tests (both classes) - it seems that the "beforeEach" hook from class 2 is applying to class 1 for some reason.
As result, the console printed fake generated accounts three times. Three because 3 tests were passed. I am not sure why this is happening.

Spying on a function used as a constructutor

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);
});
});

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.

Test that a function calls another function in an ES6 module with Sinon.js

I want to test that a function in an ES6 module calls another function using Sinon.js. Here's the basic layout of what I'm doing:
foo.js
export function bar() {
baz();
}
export function baz() {
...
}
test.js
import sinon from 'sinon';
import * as Foo from '.../foo';
describe('bar', function() {
it('should call baz', function() {
let spy = sinon.spy(Foo, 'baz');
spy.callCount.should.eql(0);
Foo.bar();
spy.calledOnce.should.eql(true);
});
});
But the spy does not pick up the call to baz(). Is there some other way I can set up the module or the test to allow sinon to pick this up? My alternative is to make some basic assertion on something baz does, but I obviously don't want to be doing that.
From what I've seen online I'm wondering if this is even possible with the code laid out as-is or if I need to restructure it to get what I want.
You're right in thinking this isn't possible with the way the module is currently structured.
When the code is executed, the baz reference inside function bar is resolved against the local implementation. You can't modify that since outside of the module code there's no access to the internals.
You do have access to exported properties, but you can't mutate these and so you can't affect the module.
One way to change that is using code like this:
let obj = {};
obj.bar = function () {
this.baz();
}
obj.baz = function() {
...
}
export default obj;
Now if you override baz in the imported object you will affect the internals of bar.
Having said that, that feels pretty clunky. Other methods of controlling behaviors exist such as dependency injection.
Also, you should consider whether or not you actually care if baz was called. In standard "black-box testing", you don't care how something is done, you only care what side effects it generated. For that, test if the side effects you expected happened and that nothing else was done.

How stub a global dependency's new instance method in nodejs with sinon.js

Sorry for the confusing title, I have no idea how to better describe it. Let's see the code:
var client = require('some-external-lib').createClient('config string');
//constructor
function MyClass(){
}
MyClass.prototype.doSomething = function(a,b){
client.doWork(a+b);
}
MyClass.prototype.doSomethingElse = function(c,d){
client.doWork(c*d);
}
module.exports = new MyClass();
Test:
var sinon = require('sinon');
var MyClass = requre('./myclass');
var client = require('some-external-lib').createClient('config string');
describe('doSomething method', function() {
it('should call client.doWork()',function(){
var stub = sinon.stub(client,'doWork');
MyClass.doSomething();
assert(stub.calledOnce); //not working! returns false
})
})
I could get it working if .createClient('xxx') is called inside each method instead, where I stub client with:
var client = require('some-external-lib');
sinon.stub(client, 'createClient').returns({doWork:function(){})
But it feels wrong to init the client everytime the method each being called.
Is there a better way to unit test code above?
NEW: I have created a minimal working demo to demonstrate what I mean: https://github.com/markni/Stackoverflow30825202 (Simply npm install && npm test, watch the test fail.) This question seeks a solution make the test pass without changing main code.
The problem arises at the place of test definition. The fact is that in Node.js it is rather difficult to do a dependency injection. While researching it in regard of your answer I came across an interesting article where DI is implemented via a custom loadmodule function. It is a rather sophisticated solution, but maybe eventually you will come to it so I think it is worth mentioning. Besides DI it gives a benefit of access to private variables and functions of the tested module.
To solve the direct problem described in your question you can stub the client creation method of the some-external-lib module.
var sinon = require('sinon');
//instantiate some-external-lib
var client = require('some-external-lib');
//stub the function of the client to create a mocked client
sinon.stub(client, 'createClient').returns({doWork:function(){})
//due to singleton nature of modules `require('some-external-lib')` inside
//myClass module will get the same client that you have already stubbed
var MyClass = require('./myclass');//inside this your stubbed version of createClient
//will be called.
//It will return a mock instead of a real client
However, if your test gets more complicated and the mocked client gets a state you will have to manually take care of resetting the state between different unit tests. Your tests should be independent of the order they are launched in. That is the most important reason to reset everything in beforeEach section
You can use beforeEach() and afterEach() hooks to stub global dependency.
var sinon = require('sinon');
var MyClass = requre('./myclass');
var client = require('some-external-lib').createClient('config string');
describe('doSomething method', function() {
beforeEach(function () {
// Init global scope here
sandbox = sinon.sandbox.create();
});
it('should call client.doWork()',function(){
var stub = sinon.stub(client,'doWork').yield();
MyClass.doSomething();
assert(stub.calledOnce); //not working! returns false
})
afterEach(function () {
// Clean up global scope here
sandbox.restore();
});
})
Part of the problem is here: var stub = sinon.stub(client,'doWork').yield();
yield doesn't return a stub. In addition, yield expects the stub to already have been called with a callback argument.
Otherwise, I think you're 95% of the way there. Instead of re-initializing for every test, you could simply remove the stub:
describe('doSomething method', function() {
it('should call client.doWork()',function(){
var stub = sinon.stub(client,'doWork');
MyClass.doSomething();
assert(stub.calledOnce);
stub.restore();
})
})
BTW, another poster suggested using Sinon sandboxes, which is a convenient way to automatically remove stubs.

Categories

Resources