Mock ES6 class to create a sequence of deterministic instances - javascript

My SUT (SoundPlayerConsumer class) creates multiple instances of a class that I need to mock (SoundPlayer). How can I mock this SoundPlayer class so that it returns a deterministic object each time it's called?
For example, my classes:
// sound-player.js
module.exports = class SoundPlayer {
constructor(sound) {
this.sound = sound;
}
playSound() {
console.log(`Playing ${sound}`);
}
}
// sound-player-consumer.js
module.exports = class SoundPlayerConsumer {
constructor(soundPlayer) {
this.helloSound = new SoundPlayer('hello');
this.goodbyeSounnd = new SoundPlayer('goodbye');
}
playSounds() {
this.helloSound.playSound();
this.goodbyeSounnd.playSound();
}
}
What I'm trying to achieve (something along these lines):
// sound-player-consumer.test.js
const mockHelloPlayer = { playSound: jest.fn() };
const mockGoodbyePlayer = { playSound: jest.fn() };
jest.mock('./sound-player', () => {
return jest.fn() // constructor function
.mockReturnValueOnce(mockHelloPlayer) // first returns the hello player
.mockReturnValueOnce(mockGoodbyePlayer); // then returns the goodbye player
});
test('hello then goodbye', () => {
const consumer = new SoundPlayerConsumer();
consumer.playSounds();
expect(mockHelloPlayer.playSound).toBeCalled();
expect(mockGoodbyePlayer.playSound).toBeCalled();
expect(mockHelloPlayer.playSound.mock.invocationCallOrder[0])
.toBeLessThan(mockGoodbyePlayer.playSound.mock.invocationCallOrder[0]);
});
This test throws an error:
TypeError: this.helloSound.playSound is not a function
I've read over the docs several times and still dont fully understand the module-factory approach - but this error is something to do with my mock objects "not wrapped in an arrow function and thus accessed before initialization after hoisting"
I'm sure there's something really obvious I'm missing here. Any help appreciated!

Related

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.

Protractor element(..) returning undefined from a separate file

I'm writing a Protractor test and in my test.step.js file I have
element(by.css('...')).getText().then(function (text) {
expect(text).to.equal('expectedText');
});
This works as expected and passes.
Instead I created a test.page.js file and in there put this.field = element(by.css('...')); and then in my step file had
"use strict"
module.exports = function exampleTest() {
var TestPage = require("...");
var testPage = new TestPage;
...
test.Then(..., function (next) {
testPage.field.getText().then(function (text) {
expect(text).to.equal('expectedText');
});
});
}
then field is undefined. I have also tried adding getText() in the page file, but again get undefined or get told that I can't call 'then' on undefined.
In my mind, this should do exactly the same thing as the first example, but I'm far from an expert with Angular or JavaScript.
test.page.js looks like:
"use strict";
module.exports = (function () {
function TestPage() {
this.field = element(by.css('...'));
}
return TestPage;
});
Hoping someone can shine some light on why this is happening and what I should do instead to be able to put the CSS selector inside a page file for re-use.
Thanks
Your code new TestPage; returns the constructor TestPage, but it's never called.
You could return the class :
function TestPage() {
this.field = element(by.css('...'));
}
module.exports = TestPage;
var TestPage = require("...");
var testPage = new TestPage;
testPage.field.getText().then(...
Or an instance of the class:
function TestPage() {
this.field = element(by.css('...'));
}
module.exports = new TestPage();
var testPage = require("...");
testPage.field.getText().then(...
The way you defined re-usable element locators looks different. I am following some thing like below
Step 1: Define a .js file which should contain the Locator objects and re-usable methods
var Login = {
PageElements: {
emailInput: element(by.css('#email')),
passwordInput: element(by.css('#password')),
loginForm: element(by.css('#form')),
},
doLogin: function doLogin() {
this.PageElements.emailInput.sendKeys('blahblah#email.com');
this.PageElements.passwordInput.sendKeys('blahblah');
this.PageElements.loginForm.submit();
},
};
module.exports = Login;
Step 2: Call these page objects in your test classes.
var LoginPage = require('../pageobjects/LoginPage.js');
it('Scenario1_Login',function(){
LoginPage.PageElements.emailInput.sendKeys('blahblah');
});
More details here

How to override a method in Object Oriented Javascript?

I was trying to implement an interface like architecture in JS as followed in C#. And met with a stumbling block. Here is the code sample:
// Interface for UIBuilder classes
function IUIBuilder() {
this.addUserToList = function () {
alert('parent: added');
};
}
// Class implementing the IUIBuilder
function ChatUIBuider() {
IUIBuilder.prototype.addUserToList = function () {
alert('child: added');
};
IUIBuilder.prototype.removeUserFromList = function () {
alert('child: removed');
};
return new IUIBuilder();
}
In the first class, I've defined a method addUserToList which I override in the second class ChatUIBuider. Also added one more method removeUserFromList to the base class using its prototype.
My issue is, the addUserToList method still invokes the parent class method even after it has got overridden in the child class. Why?
var builder = new ChatUIBuider();
builder.removeUserFromList(); // Invokes the child class method. - CORRECT
builder.addUserToList(); // Invokes the base class method- WHY??
Could anyone tell me if this is the correct way I am doing?
I suggest this construct :
function IUIBuilder() {
};
IUIBuilder.prototype.addUserToList = function () {
alert('parent: added');
};
// Class extending the IUIBuilder
function ChatUIBuider() {
}
ChatUIBuider.prototype = new IUIBuilder();
ChatUIBuider.prototype.addUserToList = function () {
alert('child: added');
};
ChatUIBuider.prototype.removeUserFromList = function () {
alert('child: removed');
};
ChatUIBuider extends IUIBuilder and inherits its functions but overrides the addUserToList function.
In the following code, both constructors will be called but only the overriding addUserToList function will be called :
var chat = new ChatUIBuider();
chat.addUserToList();
See demonstration
#Denys restructured the entire code , without exactly pointing out the issue. issue is addUserToList is not a prototype method of your parent class , it's a this method which is copied for every instance and not sahred. So just converting it to a prototype method fixes the issue.
// Interface for UIBuilder classes
function IUIBuilder() {
}
IUIBuilder.prototype.addUserToList = function () {
alert('parent: added');
};
// Class implementing the IUIBuilder
function ChatUIBuider() {
IUIBuilder.prototype.addUserToList = function () {
alert('child: added');
};
IUIBuilder.prototype.removeUserFromList = function () {
alert('child: removed');
};
return new IUIBuilder();
}
var builder = new ChatUIBuider();
builder.removeUserFromList(); // Invokes the child class method. - CORRECT
builder.addUserToList(); // Invokes the CHILD CLASS's METHOD

Categories