Mock construction of es6 collaborator - javascript

I have a class that leverages helper classes, and I'd like to verify it constructs those objects correctly. So, I'm trying to stub the "constructor" method in my classes, but I'm clearly not doing it right:
"use strict";
class Collaborator {
constructor(settings) {
console.log("Don't want this to be called!")
this.settings = settings;
}
}
class ThingToTest {
constructor(settings) {
this.helper = new Collaborator(settings);
}
}
const assert = require("assert");
const sinon = require("sinon");
describe("ThingToTest", () => {
let settings = "all the things"
context("spying on constructor", () => {
let spy = sinon.spy(Collaborator, "constructor")
after(() => spy.restore())
describe("constructor", () => {
it("creates a Collaborator with provided settings", () => {
new ThingToTest(settings);
sinon.assert.calledWith(spy, settings)
})
})
})
context("spying on prototype constructor", () => {
let spy = sinon.spy(Collaborator.prototype, "constructor")
after(() => spy.restore())
describe("constructor", () => {
it("creates a Collaborator with provided settings", () => {
new ThingToTest(settings);
sinon.assert.calledWith(spy, settings)
})
})
})
context("stub constructor", () => {
before(() => {
sinon.stub(Collaborator, "constructor", (settings) => {
console.log("This should be called so we can inspect", settings);
})
})
after(() => { Collaborator.constructor.restore() })
describe("constructor", () => {
it("creates a Collaborator with provided settings", () => {
new ThingToTest(settings);
})
})
})
context("stub prototype constructor", () => {
before(() => {
sinon.stub(Collaborator.prototype, "constructor", (settings) => {
console.log("This should be called so we can inspect", settings);
})
})
after(() => { Collaborator.prototype.constructor.restore() })
describe("constructor", () => {
it("creates a Collaborator with provided settings", () => {
new ThingToTest(settings);
})
})
})
})
Running this produces these (undesirable) results:
ThingToTest
spying on constructor
constructor
Don't want this to be called!
1) creates a Collaborator with provided settings
spying on prototype constructor
constructor
Don't want this to be called!
2) creates a Collaborator with provided settings
stub constructor
constructor
Don't want this to be called!
✓ creates a Collaborator with provided settings
stub prototype constructor
constructor
Don't want this to be called!
✓ creates a Collaborator with provided settings
It seems like stubbing is sort of working since putting the stub tests before the spy tests errors with the dreaded "TypeError: Attempted to wrap constructor which is already wrapped". So, clearly figuring out how to mock the Collaborators constructor is only half of what I'm doing wrong . . . I'm not restoring the constructor correctly either. Any suggestions?

This is not the solution I want, however for the time being I may end up using this (but please, if you have a suggestion, save me from myself):
context("checking Collaborator in a more integration style test", () => {
describe("constructor", () => {
it("creates a Collaborator with provided settings", () => {
let thing = new ThingToTest(settings);
assert.equal(thing.helper.settings, settings)
})
})
})
This passes and verifies the Collaborator has the correct settings set. But now if I want to refactor the Collaborator constructor, I'm going to break the ThingToTest. Again, I'm still holding out hope someone can suggest a way to actually unit test this class!

Not sure this is my final answer, but I ended up using proxyquire, as it's the best solution I've found so far. To show how it works, I've separated the classes under test into their own directory, and the test file in a child "test" directory. This illustrates how the paths in proxyquire work (which took me some time to figure out). So, here's what I ended up with:
/Collaborator.js
"use strict"
class Collaborator {
constructor(settings) {
console.log("Don't want this to be called!")
this.settings = settings;
}
}
module.exports = Collaborator
/ThingToTest.js
"use strict"
const Collaborator = require("./Collaborator")
class ThingToTest {
constructor(settings) {
this.helper = new Collaborator(settings)
}
}
module.exports = ThingToTest
/test/ExampleTest.js
"use strict";
const proxyquire = require('proxyquire')
const mockCollaborator = sinon.stub();
const ThingToTest = proxyquire("../ThingToTest", { "./Collaborator" : mockCollaborator })
const assert = require("assert");
const sinon = require("sinon");
describe("ThingToTest", () => {
let settings = "all the things"
context("checking Collaborator in a more integration style test", () => {
describe("constructor", () => {
it("creates a Collaborator with provided settings", () => {
let thing = new ThingToTest(settings);
assert.equal(mockCollab.firstCall.calledWith(settings))
})
})
})
})
Note how the path inside proxyquire("../ThingToTest", { "./Collaborator" : mockCollaborator }) matches what "ThingToTest" uses, not the path from the test class. I hope this help others, but I'm still open to other ideas and suggestions!

Related

How can I mock a class using jest?

How can I mock something to test something like the following codes. I tried to follow this official doc, but still not working for me https://jestjs.io/docs/es6-class-mocks#calling-jestmock-with-the-module-factory-parameter
// somefile.ts
export const myPublish = async (event: any, context: any): Promise<any> => {
const myExportHelper = await ExportHelper.getInstance({
...commonProps,
});
// just some other stuff
// just some other stuff
await myExportHelper.transfer(arg1, arg2);
};
export class ExportHelper {
constructor(
private readonly bucket: string,
private readonly read: AWS.S3,
private readonly write: AWS.S3
) {}
static async getInstance(props: {
param1: string;
}) {
...
...
return new ExportHelper(arg1, arg2, arg3);
};
async transfer(param1, param2) {
...
...
console.log('bla bla bla');
}
}
// testfile.test.ts
import { myPublish, ExportHelper } from '../somefile';
beforeEach(() => {
});
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('myTest', () => {
it('should run successfully', async () => {
// Arrange
const eventMock = {
Records: [
{
...
}
]
}
jest.mock('../somefile');
const mockActualExportHelper = jest.requireActual('../somefile').ExportHelper;
const mockGetInstanceImpl = () => {};
// this says cannot read property instances of undefined
const mockExportHelper = mockActualExportHelper.mock.instances[0];
mockExportHelper.getInstance.mockImplementation(mockGetInstanceImpl);
mockExportHelper.transfer.mockImplementation(mockGetInstanceImpl);
// Act
await myPublish(eventMock, jasmine.any({}));
// Assert
expect(ExportHelper.getInstance).toBeCalled();
expect(ExportHelper.transfer).toBeCalled(); // also not sure if this is valid to use ExportHelper
});
});
I think what you're looking for is not a mock. If you want to spy what functions are called, you will need to use the spyOn. In jest you can do the following:
jest.spyOn(MyClass, 'myMethod');
And you can also mock the implementation to subtitute the default behavior of a method, a generalist example can be like this:
jest.spyOn(MyClass, 'myMethod').mockImplementation(jest.fn());
With that said, I would rewrite the test to spy the methods from ExportHelper and avoid external calls:
import {ExportHelper, myPublish} from '../app';
beforeEach(() => {
});
afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('myTest', () => {
it('should run successfully', async () => {
// Arrange
const eventMock = {
Records: [
{
// ...
}
]
}
jest.spyOn(ExportHelper, 'getInstance').mockImplementation(jest.fn());
jest.spyOn(ExportHelper, 'transfer').mockImplementation(jest.fn());
// Act
await myPublish('arg1', 'arg2');
// Assert
expect(ExportHelper.getInstance).toBeCalled();
expect(ExportHelper.transfer).toBeCalled();
});
});
I just replaced this piece of code:
jest.mock('../somefile');
const mockActualExportHelper = jest.requireActual('../somefile').ExportHelper;
const mockGetInstanceImpl = () => {};
// this says cannot read property instances of undefined
const mockExportHelper = mockActualExportHelper.mock.instances[0];
mockExportHelper.getInstance.mockImplementation(mockGetInstanceImpl);
mockExportHelper.transfer.mockImplementation(mockGetInstanceImpl);
with
jest.spyOn(ExportHelper, 'getInstance').mockImplementation(jest.fn());
jest.spyOn(ExportHelper, 'transfer').mockImplementation(jest.fn());
Because jest will track down and watch any of these method's calls and then we can use jest's matchers to test if both of them were called. And the mockImplementation will isolate any further calls to be maded.
One thing that I noticed while reproducing your example, is that the transfer method is not being treated as a method when you get the instance from getInstance and therefore, the tests will not pass. But I think this question is not in the scope of the topic. Dunno if just happens to me.

Jest - module exports / deconstructing causing requireActual to not work or not being able to intercept original method for mocking?

I've got a controller that calls two functions and in one test I would like to see if the child function is called and in my second test see if it returns the correct number.
// ./utilities/sayMyName.js
exports.sayMyName = (name) => name;
// ./utilities/doubleNum.js
exports.doubleNum = (num) => num * 2;
// ./utilities/index.js
const { doubleNum } = require("./doubleNum");
const { sayMyName } = require("./sayMyName");
module.exports = {
doubleNum,
sayMyName,
};
// myController.js
const { doubleNum, sayMyName } = require("./utilities");
exports.doubleMyNum = (num, name) => {
const myName = sayMyName(name);
return doubleNum(num);
};
// myController.test.js
const myController = require("./myController");
const utilities = require("./utilities");
jest.mock("./utilities");
describe("doubleNum", () => {
test("should call sayMyName", () => {
myController.doubleMyNum(2, "test name");
expect(utilities.sayMyName).toHaveBeenCalledWith("test name");
});
test("should double my number", () => {
const { doubleNum } = jest.requireActual("./utilities");
expect(myController.doubleMyNum(2, "test name")).toBe(4);
});
});
First test passes however it's the second one that fails because I originally mocked the utilities module, I followed the docs and using jest.requireActual should bring back the original function but it isn't. I did read that mapping the exports like I did in index.js and using deconstructing can cause issues with intercepting a function to mock it. How can I go about getting this to work?
SORTED
After much reading and testing I'd like to think I've sorted the issue by realising that I was trying to change the implementation of function from index.js that was already imported into the myController.js file that contained the function I was testing
So for example if I want to change the implementation of only one of the exported functions from my index.js file I need to mock the module and reimport myController.js for it to have an affect. So this works:
const myController = require("./myController");
const utilities = require("./utilities");
jest.mock("./utilities");
describe("doubleNum", () => {
beforeEach(() => {
jest.resetModules();
});
test("should call sayMyName", () => {
const spy = jest.spyOn(utilities, "sayMyName");
myController.doubleMyNum(2, "test name");
expect(spy).toHaveBeenCalledWith("test name");
});
test("should double my number", () => {
jest.mock("./utilities", () => {
const { sayMyName, doubleNum } = jest.requireActual("./utilities");
return {
sayMyName,
doubleNum,
};
});
// Reimport myController for the actual utilities module to be restored
const myController = require("./myController");
expect(myController.doubleMyNum(2, "test name")).toBe(4);
});
});
Of course this could be simplified had I not used deconstruction in myController.js, hopefully someone else finds this helpful.
I think this might be because you aren’t doing the mocking in a beforeEach or beforeAll block.
Alternatively, you can look at the resetModules example here, and mock or require the module within each test block instead of for all tests: jest.resetModules

Mocking classes in Jest does not call the same method

I'm trying to mock a class that is being imported into my code with require and then testing if a method of that class is getting called.
I've created a sample setup where this issue can be replicated:
// user.js
class User {
getName() {
return "Han Solo"
}
}
module.exports = User
// user-consumer.js
const User = require('./user')
const user = new User()
module.exports.getUserName = () => {
// do things here
return user.getName()
}
// user.test.js
const userConsumer = require('./user-consumer')
const User = require('./user')
jest.mock('./user')
it('should mock', () => {
const user = new User()
jest.spyOn(user, 'getName')
userConsumer.getUserName()
expect(user.getName).toBeCalled()
})
The error I get is as follows:
If I used ES6 syntax this would work as shown on jest's documentation: https://jestjs.io/docs/en/es6-class-mocks
But I unfortunately can't use ES6 on this project as it would require a lot of refactoring.
I also tried mocking the class with the module factory parameter
jest.mock('./user', () => {
return jest.fn(() => {
return {
getName: jest.fn(),
}
})
})
It still doesn't work. When I log console.log(user.getName) in user-consumer.js:5 it does show that the method has been mocked but whatever is called in user.getName() is not the consumer function still returns "Han Solo".
I've also tried it with and without jest.spyOn and it still returns the same error.
Is this just not possible with none ES6 syntax?
The problem is that Jest spies have undocumented behaviour.
Even if prototype method is the same for all instances:
new User().getName === new User().getName
A spy is specific to an instance:
jest.spyOn(new User(), 'getName') !== jest.spyOn(new User(), 'getName')
If a specific instance is unreachable, it's a prototype that needs to be spied:
jest.spyOn(User.prototype, 'getName')
userConsumer.getUserName()
expect(User.prototype.getName).toBeCalled();
A problem with jest.mock isn't specific to ES6 syntax. In order for a spy to be available for assertions and implementation changes, it should be exposed somewhere. Declaring it outside jest.mock factory is not a good solution as it can often result in race condition described in the manual; there will be one in this case too. A more safe approach is to expose a reference as a part of module mock.
It would be more straightforward for ES module because this way class export is kept separately:
import MockedUser, { mockGetName } from './user';
jest.mock('./user', () => {
const mockGetName = jest.fn();
return {
__esModule: true,
mockGetName,
default: jest.fn(() => {
return {
getName: mockGetName
}
})
}
})
...
For CommonJS module with class (function) export, it will be efficiently exposed as class static method:
import MockedUser from './user';
jest.mock('./user', () => {
const mockGetName = jest.fn();
return Object.assign(
jest.fn(() => {
return {
getName: mockGetName
}
}),
{ mockGetName }
})
})
...
MockedUser.mockGetName.mockImplementation(...);
userConsumer.getUserName()
expect(MockedUser.mockGetName).toBeCalled();

How to change mock implementation on a per single test basis?

I'd like to change the implementation of a mocked dependency on a per single test basis by extending the default mock's behaviour and reverting it back to the original implementation when the next test executes.
More briefly, this is what I'm trying to achieve:
Mock dependency
Change/extend mock implementation in a single test
Revert back to original mock when next test executes
I'm currently using Jest v21. Here is what a typical test would look like:
// __mocks__/myModule.js
const myMockedModule = jest.genMockFromModule('../myModule');
myMockedModule.a = jest.fn(() => true);
myMockedModule.b = jest.fn(() => true);
export default myMockedModule;
// __tests__/myTest.js
import myMockedModule from '../myModule';
// Mock myModule
jest.mock('../myModule');
beforeEach(() => {
jest.clearAllMocks();
});
describe('MyTest', () => {
it('should test with default mock', () => {
myMockedModule.a(); // === true
myMockedModule.b(); // === true
});
it('should override myMockedModule.b mock result (and leave the other methods untouched)', () => {
// Extend change mock
myMockedModule.a(); // === true
myMockedModule.b(); // === 'overridden'
// Restore mock to original implementation with no side effects
});
it('should revert back to default myMockedModule mock', () => {
myMockedModule.a(); // === true
myMockedModule.b(); // === true
});
});
Here is what I've tried so far:
mockFn.mockImplementationOnce(fn)
it('should override myModule.b mock result (and leave the other methods untouched)', () => {
myMockedModule.b.mockImplementationOnce(() => 'overridden');
myModule.a(); // === true
myModule.b(); // === 'overridden'
});
Pros
Reverts back to original implementation after first call
Cons
It breaks if the test calls b multiple times
It doesn't revert to original implementation until b is not called (leaking out in the next test)
jest.doMock(moduleName, factory, options)
it('should override myModule.b mock result (and leave the other methods untouched)', () => {
jest.doMock('../myModule', () => {
return {
a: jest.fn(() => true,
b: jest.fn(() => 'overridden',
}
});
myModule.a(); // === true
myModule.b(); // === 'overridden'
});
Pros
Explicitly re-mocks on every test
Cons
Cannot define default mock implementation for all tests
Cannot extend default implementation forcing to re-declare each mocked method
Manual mocking with setter methods (as explained here)
// __mocks__/myModule.js
const myMockedModule = jest.genMockFromModule('../myModule');
let a = true;
let b = true;
myMockedModule.a = jest.fn(() => a);
myMockedModule.b = jest.fn(() => b);
myMockedModule.__setA = (value) => { a = value };
myMockedModule.__setB = (value) => { b = value };
myMockedModule.__reset = () => {
a = true;
b = true;
};
export default myMockedModule;
// __tests__/myTest.js
it('should override myModule.b mock result (and leave the other methods untouched)', () => {
myModule.__setB('overridden');
myModule.a(); // === true
myModule.b(); // === 'overridden'
myModule.__reset();
});
Pros
Full control over mocked results
Cons
Lot of boilerplate code
Hard to maintain on long term
jest.spyOn(object, methodName)
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
// Mock myModule
jest.mock('../myModule');
it('should override myModule.b mock result (and leave the other methods untouched)', () => {
const spy = jest.spyOn(myMockedModule, 'b').mockImplementation(() => 'overridden');
myMockedModule.a(); // === true
myMockedModule.b(); // === 'overridden'
// How to get back to original mocked value?
});
Cons
I can't revert mockImplementation back to the original mocked return value, therefore affecting the next tests
Use mockFn.mockImplementation(fn).
import { funcToMock } from './somewhere';
jest.mock('./somewhere');
beforeEach(() => {
funcToMock.mockImplementation(() => { /* default implementation */ });
// (funcToMock as jest.Mock)... in TS
});
test('case that needs a different implementation of funcToMock', () => {
funcToMock.mockImplementation(() => { /* implementation specific to this test */ });
// (funcToMock as jest.Mock)... in TS
// ...
});
A nice pattern for writing tests is to create a setup factory function that returns the data you need for testing the current module.
Below is some sample code following your second example although allows the provision of default and override values in a reusable way.
const spyReturns = returnValue => jest.fn(() => returnValue);
describe("scenario", () => {
beforeEach(() => {
jest.resetModules();
});
const setup = (mockOverrides) => {
const mockedFunctions = {
a: spyReturns(true),
b: spyReturns(true),
...mockOverrides
}
jest.doMock('../myModule', () => mockedFunctions)
return {
mockedModule: require('../myModule')
}
}
it("should return true for module a", () => {
const { mockedModule } = setup();
expect(mockedModule.a()).toEqual(true)
});
it("should return override for module a", () => {
const EXPECTED_VALUE = "override"
const { mockedModule } = setup({ a: spyReturns(EXPECTED_VALUE)});
expect(mockedModule.a()).toEqual(EXPECTED_VALUE)
});
});
It's important to say that you must reset modules that have been cached using jest.resetModules(). This can be done in beforeEach or a similar teardown function.
See jest object documentation for more info: https://jestjs.io/docs/jest-object.
Little late to the party, but if someone else is having issues with this.
We use TypeScript, ES6 and babel for react-native development.
We usually mock external NPM modules in the root __mocks__ directory.
I wanted to override a specific function of a module in the Auth class of aws-amplify for a specific test.
import { Auth } from 'aws-amplify';
import GetJwtToken from './GetJwtToken';
...
it('When idToken should return "123"', async () => {
const spy = jest.spyOn(Auth, 'currentSession').mockImplementation(() => ({
getIdToken: () => ({
getJwtToken: () => '123',
}),
}));
const result = await GetJwtToken();
expect(result).toBe('123');
spy.mockRestore();
});
Gist:
https://gist.github.com/thomashagstrom/e5bffe6c3e3acec592201b6892226af2
Tutorial:
https://medium.com/p/b4ac52a005d#19c5
When mocking a single method (when it's required to leave the rest of a class/module implementation intact) I discovered the following approach to be helpful to reset any implementation tweaks from individual tests.
I found this approach to be the concisest one, with no need to jest.mock something at the beginning of the file etc. You need just the code you see below to mock MyClass.methodName. Another advantage is that by default spyOn keeps the original method implementation but also saves all the stats (# of calls, arguments, results etc.) to test against, and keeping the default implementation is a must in some cases. So you have the flexibility to keep the default implementation or to change it with a simple addition of .mockImplementation as mentioned in the code below.
The code is in Typescript with comments highlighting the difference for JS (the difference is in one line, to be precise). Tested with Jest 26.6.
describe('test set', () => {
let mockedFn: jest.SpyInstance<void>; // void is the return value of the mocked function, change as necessary
// For plain JS use just: let mockedFn;
beforeEach(() => {
mockedFn = jest.spyOn(MyClass.prototype, 'methodName');
// Use the following instead if you need not to just spy but also to replace the default method implementation:
// mockedFn = jest.spyOn(MyClass.prototype, 'methodName').mockImplementation(() => {/*custom implementation*/});
});
afterEach(() => {
// Reset to the original method implementation (non-mocked) and clear all the mock data
mockedFn.mockRestore();
});
it('does first thing', () => {
/* Test with the default mock implementation */
});
it('does second thing', () => {
mockedFn.mockImplementation(() => {/*custom implementation just for this test*/});
/* Test utilising this custom mock implementation. It is reset after the test. */
});
it('does third thing', () => {
/* Another test with the default mock implementation */
});
});
I did not manage to define the mock inside the test itself so I discover that I could mock several results for the same service mock like this :
jest.mock("#/services/ApiService", () => {
return {
apiService: {
get: jest.fn()
.mockResolvedValueOnce({response: {value:"Value", label:"Test"}})
.mockResolvedValueOnce(null),
}
};
});
I hope it'll help someone :)
It's a very cool way I've discovered on this blog https://mikeborozdin.com/post/changing-jest-mocks-between-tests/
import { sayHello } from './say-hello';
import * as config from './config';
jest.mock('./config', () => ({
__esModule: true,
CAPITALIZE: null
}));
describe('say-hello', () => {
test('Capitalizes name if config requires that', () => {
config.CAPITALIZE = true;
expect(sayHello('john')).toBe('Hi, John');
});
test('does not capitalize name if config does not require that', () => {
config.CAPITALIZE = false;
expect(sayHello('john')).toBe('Hi, john');
});
});

A sinonjs stub in the "then"

I have a class A , and I want to test the eventHandler method.
class A () {
eventHandler (controller) {
controller.exec().then((() => {
this._afterHandler(); // I can't stub it.
}))
// this._afterHandler(); // I can stub it !!!
}
_afterHandler() {
xxxxx...;
}
}
This is my test code . I find that I can not stub the _afterHandler method. when the method in the then. but when I move the method to the "then" outside. I can stub it.
it('xxxx', () => {
const a = new A();
const stub = sinon.stub(a,'_afterHandler');
a.eventHandler({ exec: () => {return Promise.resolve(1)} })
sinon.assert.calledOnce(stub);
});
How can I stub the _afterHandler method ?? Thanks ~~
_afterHandler is getting stubbed by Sinon.
The problem here is that sinon.assert.calledOnce(stub) is getting called before the stubbed _afterHandler - thanks to the use of promises.
In terms of making this testable, one option is like:
class A {
eventHandler (controller) {
return controller.exec().then(() => {
this._afterHandler();
})
}
_afterHandler() {
xxxxx...;
}
}
and the test:
it('xxxx', async () => {
const a = new A();
const stub = sinon.stub(a,'_afterHandler');
await a.eventHandler({ exec: () => {return Promise.resolve(1)} });
sinon.assert.calledOnce(stub);
});

Categories

Resources