How to mock an encapsulated dependency with Jest? - javascript

I am trying to mock the return value of a method of a class which is instantiated inside of the class I am testing, without it persisting the mocked value across the rest of the tests.
Here is a very basic demo I've put together:
Mystery.ts
export class Mystery {
private max = 10;
public getNumber(): number {
return Math.random() * this.max;
}
}
TestedClass.ts
import { Mystery } from './Mystery';
export class TestedClass {
public someMethod() {
const numberGenerator = new Mystery();
return numberGenerator.getNumber();
}
}
TestedClass.test.ts
import { Mystery } from './Mystery';
import { TestedClass } from './TestedClass';
jest.mock('./Mystery');
describe('TestedClass', () => {
beforeEach(jest.clearAllMocks);
it('should return the number 1000', () => {
// I want to have Mystery.getNumber() return 1000 for this test only.
Mystery.prototype.getNumber = jest.fn().mockReturnValue(1000);
// jest.spyOn(Mystery.prototype, 'getNumber').mockReturnValue(1000);
// Mystery.prototype.getNumber = jest.fn().mockReturnValueOnce(1000);
const provider = new TestedClass();
const result = provider.someMethod();
expect(result).toEqual(1000);
});
it('should return undefined', () => {
const provider = new TestedClass();
const result = provider.someMethod();
// Expects: undefined, Received: 1000
expect(result).toEqual(undefined);
});
});
As you can see from the test, I am seeing 1000 appear in the second test which I would like to avoid. In the real scenario all of the tests will likely need to mock the return value of Mystery.getNumber() (in different ways) but I want to ensure that one test is not affecting another test as it could be now.
This current behaviour does make sense as I am changing the Mystery.prototype but I'm not sure how to mock the value for individual test in any other way. I am also aware of using composition, which would help tremendously here, but I would like to avoid this for the sake of this question.

If you're trying to mock the getNumber method for a single test case only and not for all test cases, the jest.fn().mockReturnValueOnce method should do the trick.
So instead of
Mystery.prototype.getNumber = jest.fn().mockReturnValue(1000);
use
Mystery.prototype.getNumber = jest.fn().mockReturnValueOnce(1000);

Related

TypeError: class is not a constructor

I have an issue where my tests are not running due to the following type error:
TypeError: _ViewStore.default is not a constructor
The app itself runs fine and I am assuming that the test cannot get the JourneyStore variable as it is used in the setCurrentPageFromJourney method of the ViewStore class.
Does anyone know what might be causing this?
The view store class:
class ViewStore {
currentPage: Stage;
previousPageHistory: Stage[];
constructor() {
makeAutoObservable(this);
this.currentPage = PDP_VIEWS.DEVICE;
this.previousPageHistory = [];
}
set setCurrentPage(page: Stage) {
this.currentPage = page;
this.previousPageHistory.push(page);
this.previousPageHistory = [...new Set(this.previousPageHistory)]; // removes duplicates
}
hasVisitedPage(page: Stage): boolean {
return this.previousPageHistory.includes(page);
}
setCurrentPageFromJourney() {
const journeyState = JourneyStore.journeyParams.state; // test passes if JourneyStore is not used (but then the app breaks)
switch (journeyState) {
case `a`:
this.setCurrentPage = PDP_VIEWS.DEVICE;
break;
case `b`:
this.setCurrentPage = PDP_VIEWS.INSURANCE;
break;
}
}
}
export default ViewStore;
In another file, the view store (and others, including the journey store) is instantiated like this:
import ViewStoreClass from './ViewStore';
const ViewStore = new ViewStoreClass();
I think the app uses Mobx which I am unfamiliar with and do not know whether that would make any difference.
The test:
describe('ViewStore', () => {
let store = new ViewStore();
test('should pass', () => {
expect(true).toBe(true)
})
});
By the way, the JourneyStore is just another class, with lots of properties and methods - I would show it but it will be a lot of code, and I think that may be all you need to know about it.
Any help would be greatly appreciated, thank you.

how to work with jest toHaveBeenCalled with different instance

So I am writing a test case for one of my function where I am making a call to another function of a library and I am trying to mock that function(saveCall), here is the sample code:
import { Call } from './somefolder/call';
class Demo {
var testIt = (params: any) => {
---- // Some other code
let call = new Call(params);
call.saveCall();
---- // Some other code
}
return {testIt: testIt};
}
And here is how I am writing unit test case for the same:
import { Call } from './somefolder/call';
var demo = new Demo();
test("Test it", () => {
let call = new Call({} as any);
let spyIt = jest.spyOn(call, 'saveCall').mockImplementation(()=>{console.log('here')});
demo.testIt();
expect(spyIt).toHaveBeenCalled(); // Throws error expect(jest.fn()).toHaveBeenCalled()
});
I am getting expect(jest.fn()).toHaveBeenCalled() error in expect, Now I feel I am getting error because the instance of call object in test file is different from what I have in Demo class and that is the reason spyOn doesn't know that whether the function has been called or not. I did try with mocking the entire Call.ts file but getting the same error.
Now my question is how can I create a mock and sucessfully test whether saveCall() has been called. Please note that I cannot change the implementation of testIt function.
Using jest.mock helper to mock Call class and assert on instance of the mocked class.
import { Call } from './somefolder/call';
import { Demo } from './Demo';
jest.mock('./somefolder/call'); // mock all named export items
describe("Demo", () => {
let demo: Demo;
let CallMocked: jest.Mock<Call>; // define type for mocked class
beforeEach(() => {
CallMocked = Call as any; // Actually, now Call is a mocked class
demo = new Demo();
});
test("Test it", () => {
demo.testIt();
expect(CallMocked.mock.instances[0].saveCall).toHaveBeenCalled(); // assert for mock instance
});
})

Jest manual mocking a package requiring new instance

I'm trying to use Jest manual mock to mock the behaviour of a package X used
in a project.
The usage of package X within the the actual application code is like so:
// Real.js
import X from '../lib/X.js';
export default class App {
...
execute() {
const x = new X(); // creating a new instance of X
x.doSomething(); // calling someThing() of X
}
...
}
my Jest manual mock to mimic the behaviour of X looks like:
global.__test__X = () => ({
doSomething: jest.fn(async () => console.log('mocked async do something')),
id: (Math.random() * 1000) % 10,
});
module.exports = global.__test__X;
In my test i'm trying to see how many times X was called and with what parameters
using the blow code:
jest.mock('../X');
import Real from '../Real';
const X = require('../X');
describe('Testing...', async () => {
it('DoSomething should print mocked console statement', async () => {
Real.execute(); // this would internally call X.doSomething()
const x = new X();
console.log(x.doSomething.mock.calls); // gives []
});
});
Using above, I'm trying to check how many times X was called but can't figure out what
i'm doing wrong since mock.calls is always []. Note that the mock is getting
executed as i can see mocked async do something.
There's a complete explanation for doing this with jest.mock('./SomeClass'); that applies to this question. "ES6 class, Automatic mock" .
let's get started.
// ./lib/X.js
export default class X {
constructor () {
this.id = '1234567890';
}
doSomething = () => {
return 'Original X';
}
}
Note, the above code has never been called during the test.
This is the resource we want to test, what I mean is, in this class create objects by the class or module that is mocked. We want to make a fake version instead of the original.
// Real.js
import X from './lib/X.js';
export default class App {
constructor() {
this.x = new X(); // creating a new instance of X
}
execute = () => {
this.x.doSomething(); // calling someThing() of X
}
}
Accepts a function that should be used as the implementation of the mock. So what we will do is using manual mocks ( __ mocks __ folder) to mock ES6 classes.
// ./__mocks__/lib/X.js
module.exports = jest.fn().mockImplementation(() => {
return {
doSomething: jest.fn(() => 'Mocking Original X'),
id: (Math.random() * 1000) % 10
}
});
When we import './lib/X.js' on our test file, Now, in order to test this method without actually hitting the library (and thus creating slow and fragile tests), we immediately use the mock the './lib/X.js' module.
// Real.test.js
import X from './lib/X.js';
import Real from './Real';
jest.mock('./lib/X.js'); // // X module is now a mock constructor
describe('Testing', async () => {
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
X.mockClear();
});
it('DoSomething should print mocked correct statement', async () => {
// Ensure our mockClear() is clearing out previous calls to the constructor
expect(X).not.toHaveBeenCalled();
const real = new Real();
expect(X).toHaveBeenCalledTimes(1); // Constructor has been called X.js
real.execute();
// mock.instances is available with automatic mocks:
const mockXInstance = X.mock.instances[0];
const mockDoSomething = mockXInstance.doSomething;
expect(mockDoSomething).toHaveBeenCalledTimes(1);
expect(mockDoSomething.mock.calls[0][0]).toEqual('Mocking Original X');
});
});
maybe this is not enough to answer, at least this explains how mock works in similar cases

Mock Es6 classes using Jest

I'm trying to mock an ES6 class with a constructor that receives parameters, and then mock different class functions on the class to continue with testing, using Jest.
Problem is I can't find any documents on how to approach this problem. I've already seen this post, but it doesn't resolve my problem, because the OP in fact didn't even need to mock the class! The other answer in that post also doesn't elaborate at all, doesn't point to any documentation online and will not lead to reproduceable knowledge, since it's just a block of code.
So say I have the following class:
//socket.js;
module.exports = class Socket extends EventEmitter {
constructor(id, password) {
super();
this.id = id;
this.password = password;
this.state = constants.socket.INITIALIZING;
}
connect() {
// Well this connects and so on...
}
};
//__tests__/socket.js
jest.mock('./../socket');
const Socket = require('./../socket');
const socket = new Socket(1, 'password');
expect(Socket).toHaveBeenCalledTimes(1);
socket.connect()
expect(Socket.mock.calls[0][1]).toBe(1);
expect(Socket.mock.calls[0][2]).toBe('password');
As obvious, the way I'm trying to mock Socket and the class function connect on it is wrong, but I can't find the right way to do so.
Please explain, in your answer, the logical steps you make to mock this and why each of them is necessary + provide external links to Jest official docs if possible!
Thanks for the help!
Update:
All this info and more has now been added to the Jest docs in a new guide, "ES6 Class Mocks."
Full disclosure: I wrote it. :-)
The key to mocking ES6 classes is knowing that an ES6 class is a function. Therefore, the mock must also be a function.
Call jest.mock('./mocked-class.js');, and also import './mocked-class.js'.
For any class methods you want to track calls to, create a variable that points to a mock function, like this: const mockedMethod = jest.fn();. Use those in the next step.
Call MockedClass.mockImplementation(). Pass in an arrow function that returns an object containing any mocked methods, each set to its own mock function (created in step 2).
The same thing can be done using manual mocks (__mocks__ folder) to mock ES6 classes. In this case, the exported mock is created by calling jest.fn().mockImplementation(), with the same argument described in (3) above. This creates a mock function. In this case, you'll also need to export any mocked methods you want to spy on.
The same thing can be done by calling jest.mock('mocked-class.js', factoryFunction), where factoryFunction is again the same argument passed in 3 and 4 above.
An example is worth a thousand words, so here's the code.
Also, there's a repo demonstrating all of this, here:
https://github.com/jonathan-stone/jest-es6-classes-demo/tree/mocks-working
First, for your code
if you were to add the following setup code, your tests should pass:
const connectMock = jest.fn(); // Lets you check if `connect()` was called, if you want
Socket.mockImplementation(() => {
return {
connect: connectMock
};
});
(Note, in your code: Socket.mock.calls[0][1] should be [0][0], and [0][2] should be [0][1]. )
Next, a contrived example
with some explanation inline.
mocked-class.js. Note, this code is never called during the test.
export default class MockedClass {
constructor() {
console.log('Constructed');
}
mockedMethod() {
console.log('Called mockedMethod');
}
}
mocked-class-consumer.js. This class creates an object using the mocked class. We want it to create a mocked version instead of the real thing.
import MockedClass from './mocked-class';
export default class MockedClassConsumer {
constructor() {
this.mockedClassInstance = new MockedClass('yo');
this.mockedClassInstance.mockedMethod('bro');
}
}
mocked-class-consumer.test.js - the test:
import MockedClassConsumer from './mocked-class-consumer';
import MockedClass from './mocked-class';
jest.mock('./mocked-class'); // Mocks the function that creates the class; replaces it with a function that returns undefined.
// console.log(MockedClass()); // logs 'undefined'
let mockedClassConsumer;
const mockedMethodImpl = jest.fn();
beforeAll(() => {
MockedClass.mockImplementation(() => {
// Replace the class-creation method with this mock version.
return {
mockedMethod: mockedMethodImpl // Populate the method with a reference to a mock created with jest.fn().
};
});
});
beforeEach(() => {
MockedClass.mockClear();
mockedMethodImpl.mockClear();
});
it('The MockedClassConsumer instance can be created', () => {
const mockedClassConsumer = new MockedClassConsumer();
// console.log(MockedClass()); // logs a jest-created object with a mockedMethod: property, because the mockImplementation has been set now.
expect(mockedClassConsumer).toBeTruthy();
});
it('We can check if the consumer called the class constructor', () => {
expect(MockedClass).not.toHaveBeenCalled(); // Ensure our mockClear() is clearing out previous calls to the constructor
const mockedClassConsumer = new MockedClassConsumer();
expect(MockedClass).toHaveBeenCalled(); // Constructor has been called
expect(MockedClass.mock.calls[0][0]).toEqual('yo'); // ... with the string 'yo'
});
it('We can check if the consumer called a method on the class instance', () => {
const mockedClassConsumer = new MockedClassConsumer();
expect(mockedMethodImpl).toHaveBeenCalledWith('bro');
// Checking for method call using the stored reference to the mock function
// It would be nice if there were a way to do this directly from MockedClass.mock
});
For me this kind of Replacing Real Class with mocked one worked.
// Content of real.test.ts
jest.mock("../RealClass", () => {
const mockedModule = jest.requireActual(
"../test/__mocks__/RealClass"
);
return {
...mockedModule,
};
});
var codeTest = require("../real");
it("test-real", async () => {
let result = await codeTest.handler();
expect(result).toMatch(/mocked.thing/);
});
// Content of real.ts
import {RealClass} from "../RealClass";
export const handler = {
let rc = new RealClass({doing:'something'});
return rc.realMethod("myWord");
}
// Content of ../RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "The.real.deal "+input;
}
// Content of ../test/__mocks__/RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "mocked.thing "+input;
}
Sorry if I misspelled something, but I'm writing it on the fly.

Jasmine Unit Test: should I test the logic or the actual mocked data?

when performing unit testing is it better to test the literal result that I expect hard-coding it into the test (expect(x).toBe(17)), or is it better to test the logic and not the specific mock data that I am using (expect(x).toBe(mockedData.value))
The first approach seems safer because I am sure that the test is actually testing the literal result that I expect, however the second approach is more flexible since it allows me to test the logic rather than worry about the mock data (which I can also change later without having to rewrite the test itself)
What are the advantages/disadvantages of either approach? What is the best practice in these cases?
Following is a quick example:
// MockedData is a very long array of complex objects
// each of them has a property 'value' of type number
import mockedData from 'data.mock';
class ClassToTest {
private data;
constructor(data) {
this.data = data;
}
plusOne(): number {
return this.data.value + 1;
}
}
describe('test', () => {
let instance: ClassToTest;
beforeEach(() => {
instance = new ClassToTest(mockedData[0]);
})
it('plusOne() should return the property "value" plus one', () => {
// Should I write this...
expect(instance.plusOne()).toBe(mockedData[0] + 1);
// ...or this?
expect(instance.plusOne()).toBe(17); // Because I know that mockedData[0].value is 16
})
});
Thank you very much!! :)
In your test you want to test your unit, which in your case is the logic inside of your plusOne() function. So you want to only know if something changes inside the function.
The most dangerous path is to use expect(instance.plusOne()).toBe(17);, because if someone changes your logic to return this.data.value + 2;, you will never spot from test only if the problem is in the function logic or in the mockedData.
The less dangerous approach is to use expect(instance.plusOne()).toBe(mockedData[0] + 1);, because this will tell you if the logic in your function change. Still not optimal, since you depend on an external mock to run your test that you don't need. Why would you want to depend on an external mocked data to test your unit?
The best way to test your unit logic here is to do something like this:
describe('test', () => {
let instance: ClassToTest;
const mockedValue = 1;
beforeEach(() => {
instance = new ClassToTest(mockedValue);
})
it('plusOne() should return the property "value" plus one', () => {
expect(instance.plusOne()).toBe(mockedValue + 1);
})
});
Then, you can implement separate tests for your service, here you only test the logic inside plusOne().

Categories