Unit Testing ES6 Class with External Dependency - javascript

I'm trying to set a unit testing boilerplate for my company. Our front end projects are built with ES6 classes and have a dependency to our core product. The front end code gets wrapped through a build process in a whole other block of code that is basically a closure and captures the dependency. So we don't have to manually import it in order to use it.
Let's say the dependency is called productScope and it's an object that has some DOM models, internal APIs and parameters among many other things necessary for each project. At the moment, Mocha throws ReferenceError: productScope is not defined. How can I mock this object? Or should I just use the actual object?
Example:
class someClass {
constructor() {
const id = productScope.items[0].id
const item = productScope.domModel.querySelector('.some-div')
item.classList.add(`added-${id}`)
}
}
This get wrapped in core code like below:
(function(productScope) {
// front end code goes here
}(productScope)
Testing file:
import someClass from '../../js/someClass'
describe('someClass', function() {
const someClass = new someClass()
it('should be a class', function() {
console.log(someClass)
});
});

You can try something like this
describe('#someClass', () => {
let someClass;
beforeEach(() => {
global.productScope = {
// mocking productScope object
};
});
it('should be a class', () => {
someClass = new SomeClass;
console.log(someClass);
});
afterEach(() => {
delete global.productScope;
});
});
or alternatively if you want more specific mock logic for each test case
describe('#someClass', () => {
let someClass;
it('should be a class', () => {
global.productScope = {
// mocking productScope object
};
// Test logic start
someClass = new SomeClass;
console.log(someClass);
// Test logic end
delete global.productScope;
});
});

Looks like productScope is a global variable.
Something like this should work for you.
import someClass from '../../js/someClass';
describe('someClass', function() {
let someClass;
beforeEach(() => {
global.productScope = {
// you mock definition
someClass = new someClass();
};
});
it('should be a class', function() {
console.log(someClass)
});
});

I'm with other answers as well, as managing global variables seems to be the simplest and most straightforward solution.
However, you can use toString to get class's string representation, and eval it to bind to closure's scope:
class someClass {
constructor() {
this.id = scopedId
}
}
// pass class as an argument
function scopeFactory(classDef) {
// define scoped data
let scopedId = 2;
// eval is used to bind class to the local closure
// so `scopedId` will be in charge
return eval("(" + classDef + ")");
}
const scopedSomeClass = scopeFactory(someClass);
console.log(new scopedSomeClass)
Note that eval(someCLass.toString()) doesn't work without parentheses.
You can add it as a helper function, into your project.

Related

How to mock a specific method of a class whilst keeping the implementation of all other methods with jest when the class instance isn't accessible?

Based on this question (How to mock instance methods of a class mocked with jest.mock?), how can a specific method be mocked whilst keeping the implementation of all other methods?
There's a similar question (Jest: How to mock one specific method of a class) but this only applies if the class instance is available outside it's calling class so this wouldn't work if the class instance was inside a constructor like in this question (How to mock a constructor instantiated class instance using jest?).
For example, the Logger class is mocked to have only method1 mocked but then method2 is missing, resulting in an error:
// Logger.ts
export default Logger() {
constructor() {}
method1() {
return 'method1';
}
method2() {
return 'method2';
}
}
// Logger.test.ts
import Logger from './Logger';
jest.mock("./Logger", () => {
return {
default: class mockLogger {
method1() {
return 'mocked';
}
},
__esModule: true,
};
});
describe("Logger", () => {
it("calls logger.method1() & logger.method2 on instantiation where only method1 is mocked", () => {
const logger = new Logger(); // Assume this is called in the constructor of another object.
expect(logger.method1()).toBe('mocked');
expect(logger.method2()).toBe('method2'); // TypeError: logger.method2 is not a function.
});
});
One solution is to extend the Logger class but this results in an undefined error as the Logger is already mocked:
// ...
jest.mock("./Logger", () => {
return {
default: class mockLogger extends Logger {
override method1() {
return 'mocked';
}
},
__esModule: true,
};
});
// ...
expect(logger.method2()).toBe('method2'); // TypeError: Cannot read property 'default' of undefined
Therefore, what could be the correct way to mock only method1 but keep method2's original implementation?
You can use jest.spyOn and provide a mock implementation for method1.
// Logger.test.ts
import Logger from './Logger';
jest.spyOn(Logger.prototype, "method1").mockImplementation(() => "mocked")
describe("Logger", () => {
it("calls method1 & method2 but only method1 is mocked", () => {
const l = new Logger();
expect(l.method1()).toBe("mocked");
expect(l.method2()).toBe("method2");
})
})
But in case you have many methods and you want to mock each one of them except one single method, then you can get the original implementation of this one single method using jest.requireActual.
// Logger.test.ts
import Logger from "./Logger";
const mockMethod1 = jest.fn().mockReturnValue("mocked");
const mockMethod3 = jest.fn().mockReturnValue("mocked");
const mockMethod4 = jest.fn().mockReturnValue("mocked");
const mockMethod5 = jest.fn().mockReturnValue("mocked");
jest.mock("./Logger", () =>
jest.fn().mockImplementation(() => ({
method1: mockMethod1,
method2: jest.requireActual("./Logger").default.prototype.method2,
method3: mockMethod3,
method4: mockMethod4,
method5: mockMethod5,
}))
);
describe("Logger", () => {
it("calls all methods but only method1 is mocked", () => {
const l = new Logger();
expect(l.method1()).toBe("mocked");
expect(l.method2()).toBe("method2");
expect(l.method3()).toBe("mocked");
expect(l.method4()).toBe("mocked");
expect(l.method5()).toBe("mocked");
});
});
Note: You don't need to define an ES6 class for mocking, a constructor function also just works fine because ES6 classes are actually just syntactic sugar for constructor functions.
Mocking the prototype works:
describe("Logger", () => {
it("calls logger.method1() & logger.method2 on instantiation where only method1 is mocked", () => {
Logger.prototype.method1 = jest.fn(() => 'mocked');
const logger = new Logger();
expect(logger.method1()).toBe('mocked');
expect(logger.method2()).toBe('method2');
});
});
However, I'm not sure if this is the correct way to mock a specific method when the class instance isn't accessible so I'll leave the question open for while in case there are better solutions.

How to test functions in a function using Jest

I have some code that has functions inside functions, and I want to be able to unit test the functions inside the parent function.
I am looking to have tests that unit test these and spy on them (both requirements are needed).
Example:
export default parentFunction = () => {
const innerFunction = () => {
//that does stuff
}
const anotherInnerFunction = () => {
//that does more stuff
}
//and at some point, the functions are called
//like this
innerFunction()
const anotherFunction = () => {
//or like this
anotherInnerFunction()
}
}
I have not been able to find a way to test these inner functions. I have tried the following.
Example test
import parentFunction from "myfile"
it("should call innerFunction", () => {
//this causes an error in jest
const innerFunctionSpy = jest.spyOn(parentFunction, "innerFunction")
//..etc
expect(innerFunctionSpy).toHaveBeenCalled()
})
it("will return a value from anotherInnerFunction", () => {
//this does not work
const value = parentFunction.anotherInnerFunction()
//this also does not work
const value = parentFunction().anotherInnerFunction()
//..etc
})
Does the parent function need to be refactored in order to be able to tests these inner functions? If my parent function was an object then I could test these, however, I am not sure if I can refactor my code to work like this.
For example
export default parentFunction = {
innerFunction: () => {
//that does stuff
},
//more code
}
You cannot access the variables or functions scoped inside another function in JavaScript. Unless you explicitly expose them by returning them from that function or export them from the module. This is not about Jest, this is how it works in JavaScript.
jest.spyOn(parentFunction, "innerFunction")
The above line of code indicates to Jest that the innerFunction function is set as a property of the parentFunction object but that is not the case. In fact innerFunction is a function scoped inside the parentFunction which cannot be accessed from outside of the scope of parentFunction. Unless you return it explicitly or define it on the module level scope and then export it.
But the inner workings or the implementation details of such inner functions should not be exposed, but if it is needed it should be marked as such using an _ before its name, take the following example:
//scoped to the module
const _innerFunction = () => {
//that does stuff
}
//scoped to the module
const _anotherInnerFunction = () => {
//that does more stuff
}
//exported as a public API
const anotherFunction = () => {
_anotherInnerFunction()
}
const publicApi = {
anotherFunction,
// expose the private functions for unit tests
_innerFunction,
_anotherInnerFunction
}
export default publicApi;
Then in your Jest test case:
import publicApi from "myfile"
it("should call anotherFunction", () => {
const anotherFunctionSpy = jest.spyOn(publicApi, "anotherFunction")
//..etc
expect(anotherFunctionSpy ).toHaveBeenCalled()
})
it("should call _innerFunction", () => {
const innerFunctionSpy = jest.spyOn(publicApi, "_innerFunction")
//..etc
expect(innerFunctionSpy ).toHaveBeenCalled()
})

Spy on a method of an object created dynamically during test execution

I need to spyOn a method of an object created dynamically inside another method
So consider the following:
public doStuff = () => {
const myThing = new MyThing();
myThing.doSomethingElse().then((data) => {
//do more stuff here...
})
}
I want to therefore spyOn the instance of MyThing and the call to doSomethingElse().
I did come across a solution here which make use of the object's prototype, which I attempted like so
spyOn(MyThing.prototype, 'doSomethingElse').and.returnValue(Promise.resolve({foo: 'bar'}));
But this does not work after I call doStuff() in my tests, I get an error:
Error: : doSomethingElse() method does not exist
But I know this method is fine, since it runs as expected locally.
I am unsure how to proceed, can anyone assist?
Thanks
You might be missing something in your test. This is a simple test and you can see it works as you expected
require("jasmine");
class MyThing {
async doSomethingElse() {
return { bar: "foo" };
}
}
class Stuff {
doStuff() {
const myThing = new MyThing();
myThing.doSomethingElse().then(data => {
console.log(data);
});
}
}
describe("doSomethingElse", () => {
it("toHaveBeenCalled", () => {
spyOn(MyThing.prototype, "doSomethingElse").and.returnValue(
Promise.resolve({ foo: "bar123" })
);
const stuff = new Stuff();
stuff.doStuff();
expect(MyThing.prototype.doSomethingElse).toHaveBeenCalled();
});
});
The key is to use the spyOn before you instantiate the class that contains the doStuff function. If we move the const stuff = new Stuff(); above the spyOn it fails.
Hope it helps
You can create a spy object with mock method by passing in an object where the property names represent returned data for methods.
describe('test', () => {
let mock;
beforeEach(() => {
mock = jasmine.createSpyObj('mock', {
doSomethingElse: Promise.resolve({foo: 'bar'})
});
});
it('call mock', async () => {
const result = await mock.doSomethingElse();
expect(result.foo).toEqual('bar');
});
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.4.0/jasmine.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.4.0/jasmine.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.4.0/jasmine-html.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.4.0/boot.js"></script>

How to test class instance inside a function with Jest

I have the following hypothetical scenario:
// file MyClass.js in an external package
class MyClass {
myfunc = () => {
// do something
}
}
// file in my project
function myFunctionToBeTested() {
const instance = new MyClass()
instance.myFunc()
}
I need to create a test with Jest that makes sure instance.myFunc was called
One of the option is to replace MyClass module with mock implementation
const mockmyfunc = jest.fn()
jest.mock("path/to/external/package/MyClass", () => {
return jest.fn().mockImplementation(() => {
return {myfunc: mockmyfunc}
})
})
And then write following test
it("Test myfunc called in functionToBeTested", () => {
functionToBeTested()
expect(mockmyfunc).toHaveBeenCalled()
})
Note that this is not the only way, you can dive into https://facebook.github.io/jest/docs/en/es6-class-mocks.html for other alternatives.
Update
If the myfunc would be an actual function (which i guess is not an option since it's external package?)
export class MyClass {
myFunc() {
// do smth
}
}
and you would not need to replace the implementation, you could be using jest's automock
import MyClass from "path/to/external/package/MyClass"
jest.mock("path/to/external/package/MyClass")
it("Test myfunc called in functionToBeTested", () => {
functionToBeTested()
const mockMyFunc = MyClass.mock.instances[0].myFunc
expect(mockMyFunc).toHaveBeenCalled()
})
you can mock out the class and assign the default export of that file to a variable as follows:
jest.mock('../../utils/api/api');
const FakeClass = require('../someFile.js').default;
then access calls to a function on your mock class like this:
FakeClass.prototype.myFunc.mock.calls

ES6 import nested function - mocha

I am using ES6, and I want to start testing using mocha & chai.
My current test file code is :
const assert = require('chai').assert;
var app = require('../../../../src/app/login/loginController').default;
describe('login Controller tests', function(){
it('no idea ', function(){
let result = app();
assert.equal(result, 'hello');
})
})
and my loginController.js is :
class LoginController {
checkout(){
return 'hello';
}
}
export default LoginController
I want to import the 'checkout' function into a variable inside my test file, but so far I am able to import only the class.
Will appreciate any help, thanks !
You cannot import methods directly from classes. If you want to import a function without a class as intermediary, then you need to define the function outside the class. Or if you really meant checkout to be an instance method, then you need to call it on an instance.
Here's an example file derived from yours:
export class LoginController {
// Satic function
static moo() {
return "I'm mooing";
}
// Instance method
checkout() {
return "hello";
}
}
// A standalone function.
export function something() {
return "This is something!";
}
And a test file that exercises all functions, adapted from the file you show in your question:
const assert = require('chai').assert;
// Short of using something to preprocess import statements during
// testing... use destructuring.
const { LoginController, something } = require('./loginController');
describe('login Controller tests', function(){
it('checkout', function(){
// It not make sense to call it without ``new``.
let result = new LoginController();
// You get an instance method from an instance.
assert.equal(result.checkout(), 'hello');
});
it('moo', function(){
// You get the static function from the class.
assert.equal(LoginController.moo(), 'I\'m mooing');
});
it('something', function(){
// Something is exported directly by the module
assert.equal(something(), 'This is something!');
});
});

Categories

Resources