How to monkey patch a single method when using jest.mock - javascript

I'm using Jest to test a JS project which imports a third party library. I've been able to successfully mock the third-party library by doing this at the top of my test file:
jest.mock('third-party');
But now, I need to customize the mock implementation of a single method inside the third-party library. Let give diagram how the third-party library is structured because I think that's where I'm getting tripped up:
Third-Party Library Package
Exports Constructor1
properties
tons of instance methods...
Exports Constructor2
properties...
instance methodA
instance methodB <- This is what I want to monkey patch.
I want to monkey patch this because mocking this library currently gives me this in my test:
import { Constructor2 } from 'third-party';
jest.mock('third-party');
describe('Thing', () => {
var instance
beforeEach(() => {
instance = new Thing();
instance.constuctor2instance = new Constructor2();
instance.controls = instance.constuctor2instance.methodB();
// methodB returns nothing because it's mocked. I want it to return a custom implementation.
});
test('test 1', () => {
// Fake test just for example
expect(instance.constuctor2instance).toBeInstanceOf(Constructor2); // Success
jest.spyOn(instance.controls, 'nestedFunction'); // Fails because instance.controls is undefined
});
});
So, how can I provide a custom implementation for methodB without having to define the implementation of the whole third party library or even the entire Constructor2...just one method?
**EDIT with solution from below from ** #Teneff
jest.mock('third-party');
const mockControls = {
nestedFunction: jest.fn()
};
beforeEach(() => {
Constructor2.prototype.controls.mockImplementation(() => mockControls);
});
afterEach(() => {
jest.resetAllMocks();
});

You can use Constructor2's prototype like this
const mockControls = {
nestedFunction: jest.fn(),
};
Constructor2.prototype.methodB.mockImplementation(() => mockControls);
and you won't have to spy on it, you'd be able to make assertions like so:
expect(mockControls.nestedFunction).toHaveBeenCalledWith(...);

Related

Create React App changes behaviour of jest.fn() when mocking async function

I am confused about the below behaviour of jest.fn() when run from a clean CRA project created using npx create-react-app jest-fn-behaviour.
Example:
describe("jest.fn behaviour", () => {
const getFunc = async () => {
return new Promise((res) => {
setTimeout(() => {
res("some-response");
}, 500)
});;
}
const getFuncOuterMock = jest.fn(getFunc);
test("works fine", async () => {
const getFuncInnerMock = jest.fn(getFunc);
const result = await getFuncInnerMock();
expect(result).toBe("some-response"); // passes
})
test("does not work", async () => {
const result = await getFuncOuterMock();
expect(result).toBe("some-response"); // fails - Received: undefined
})
});
The above test will work as expected in a clean JavaScript project but not in a CRA project.
Can someone please explain why the second test fails? It appears to me that when mocking an async function jest.fn() will not work as expected when called within a non-async function (e.g. describe above). It will work only when called within an async function (test above). But why would CRA alter the behaviour in such a way?
The reason for this is, as I mentioned in another answer, that CRA's default Jest setup includes the following line:
resetMocks: true,
Per the Jest docs, that means (emphasis mine):
Automatically reset mock state before every test. Equivalent to
calling jest.resetAllMocks() before each test. This will lead to
any mocks having their fake implementations removed but does not
restore their initial implementation.
As I pointed out in the comments, your mock is created at test discovery time, when Jest is locating all of the specs and calling the describe (but not it/test) callbacks, not at execution time, when it calls the spec callbacks. Therefore its mock implementation is pointless, as it's cleared before any test gets to run.
Instead, you have three options:
As you've seen, creating the mock inside the test itself works. Reconfiguring an existing mock inside the test would also work, e.g. getFuncOuterMock.mockImplementation(getFunc) (or just getFuncOuterMock.mockResolvedValue("some-response")).
You could move the mock creation and/or configuration into a beforeEach callback; these are executed after all the mocks get reset:
describe("jest.fn behaviour", () => {
let getFuncOuterMock;
// or `const getFuncOuterMock = jest.fn();`
beforeEach(() => {
getFuncOuterMock = jest.fn(getFunc);
// or `getFuncOuterMock.mockImplementation(getFunc);`
});
...
});
resetMocks is one of CRA's supported keys for overriding Jest configuration, so you could add:
"jest": {
"resetMocks": false
},
into your package.json.
However, note that this can lead to false positive tests where you expect(someMock).toHaveBeenCalledWith(some, args) and it passes due to an interaction with the mock in a different test. If you're going to disable the automatic resetting, you should also change the implementation to create the mock in beforeEach (i.e. the let getFuncOuterMock; example in option 2) to avoid state leaking between tests.
Note that this is nothing to do with sync vs. async, or anything other than mock lifecycle; you'd see the same behaviour with the following example in a CRA project (or a vanilla JS project with the resetMocks: true Jest configuration):
describe("the problem", () => {
const mock = jest.fn(() => "foo");
it("got reset before I was executed", () => {
expect(mock()).toEqual("foo");
});
});
● the problem › got reset before I was executed
expect(received).toEqual(expected) // deep equality
Expected: "foo"
Received: undefined

Jest testing function that calls eval

I have a Vue TS project created with vue-cli 3.0.
This is the function I will test:
public hopJavascript(url: string) {
eval(url);
}
And this is my test function using Jest framework:
test('navigation using javascript', () => {
const url = "someurl";
hopJavascript(url);
expect(eval).toBeCalled();
});
Now i get this message,test failed console logging which is telling me that I need a mocked version of eval.
How can i mock eval ?
Update
It seems you can't always override eval based on your JS environment (browser or node). See the answer to "How override eval function in javascript?" for more information
Original answer
I think you can redefine eval in your test file:
global.eval = jest.fn()
You need to track your function with spyOn().
This solution should work for you.
import MyClass from '../MyClass';
test('navigation using javascript', () => {
const url = "someurl";
const spy = jest.spyOn(MyClass, 'eval');
MyClass.hopJavascript(url);
expect(spy).toHaveBeenCalled();
});

Mocha / Sinon Unit test JS Class and Instance issue

I'm trying to unit test some express middleware which has dependencies on some classes I've made.
Middleware.js
const MyClass = require('../../lib/MyClass');
const myClassInstance = new MyClass();
function someMiddleware(req, res) {
myClassInstance.get().then(function(resp) {
res.render('somefile.html');
});
};
Test.js
const MyClass = require('../../lib/MyClass');
const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;
// File we are testing
const someMiddleware = require('path/to/middleware');
MyClassMock = sinon.spy(function() {
return sinon.createStubInstance(MyClassMock);
});
describe('My Middleware', function() {
let renderSpy, classStub;
let res = {
render: function() {}
}
beforeEach(function() {
renderSpy = sinon.stub(res, 'render');
})
afterEach(function() {
renderSpy.restore();
})
it('should call render function', function() {
someMiddleware.someMiddleware(req, res);
expect(renderSpy.calledOnce);
});
});
I have tried a bunch of things however I can't quite seem to actually mock this class and mock the instance created! when it runs it will try to run the actual class with it's related dependencies inside.
Cheers folks!
Your Intent: replace/stub the dependencies of your middleware.
Your problem: this isn't possible from the outside of your module. You somehow need to "intercept" which code will be used.
There are two options, both of which I have written at length about in other places (1, 2, 3), so I'll do the shortform here:
Manual dependency injection
In your middleware module, provide a __setMyClassInstance(stubbedInstance) method that is only called from tests.
Pro: very easy, no frameworks needed
Con: test-only code that opens up your module
Link seams
This is about replacing require() calls in your code with another module loader that returns objects of your liking. In your test code:
const myMiddleware = proxyquire('../../src/middleware', { '../../lib/MyClass': myStubbedInstance })
Pro: Achieves the same as DI, but requires no code changes in the module
Con: not as clear cut what is going on, requires learning a new dependency
If you are stuck after looking at these very brief explanations, I suggest looking at my provided links for long-form explanations and links to tutorials on Link Seam libraries such as proxyquire and rewire.

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.

Accessing a Meteor Template Helper Function in Jasmine for Integration Testing

I'm trying to run Jasmine client integration tests on a meteor project. I'm using meteor 0.9.4, and the sanjo:jasmine package for Jasmine.
I have written a test which looks like:
describe("Template.dashboard.tasks", function() {
it("ela displays correct assessment", function() {
Session.set("selected_subject", "math");
Session.set('selected_grade', "1");
tasks = Template.dashboard.tasks();
expect(true).toBe(true);
});
});
I get an error before it can get to the end of the test:
Cannot read property 'tasks' of undefined
This means that Template.dashboard does not exist within the scope of this test.
Template.dashboard.tasks() is a helper function which works completely, and it is in a js file within a view folder. Regular Jasmine tests work as expected, but as soon as I try to use one of my own functions from another file, it doesn't work.
My question is: Is there something I need to do to give the Jasmine test access to my template helper functions?
In Meteor, Template helper functions used to be formatted like this:
Template.dashboard.tasks = function () {
...
};
But that has been deprecated, and the new format is:
Template.dashboard.helpers({
tasks: function(){
...
}
});
In Jasmine, with the previous formatting, you could access helper functions like:
Template.dashboard.tasks();
But now you must call helper functions like this:
Template.dashboard.__helpers[' tasks']();
Sanjo (the original author of the meteor-jasmine repo) suggested using a function like this to make it easier to call helper functions (especially if the syntax ends up getting changed again):
function callHelper(template, helperName, context = {}, args = []) {
template.__helpers[` ${helperName}`].apply(context, args);
}
An updated answer to this question for Meteor 1.3 (sorry I use mocha, but it does not affect the answer):
Template.foo and Template.foo helpers won't be eagerly set up when testing, so you need to import foo.html then foo.js.
Here is an example :
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Foo } from '/collections/foo.js';
import { assert } from 'meteor/practicalmeteor:chai';
import './foo.html'; // now Template.foo is defined
import './foo.js'; // now Template.foo.__helpers[' bar'] is defined
describe('foo handlers', () => {
it("Should test bar", () => {
// don't forget the space, helper key is ' bar' not 'bar'
var bar = Template.foo.__helpers[' bar'].apply({foo:"bar"},3);
assert.Equal(bar,'bar');
});
});
Of course as said before, you should definitely encapsulate the weird Template.foo.__helpers[' bar'].apply(context,args) into a nice, clean helper.
tests on server part are running nicely from start, and there is indeed one more thing to do in order to run tests on the frontend part. I'll try to find you that.
In addition, consider reading or reading again the famous and explicit article from Dr. Llama's Blog related to Jasmin/Meteor : Bullet-proof Meteor applications with Velocity, Unit Testing, Integration Testing and Jasmine

Categories

Resources