Mocking / Spying helper function in Angular Unit tests - javascript

I have a helper file with multiple function:
myHelpers.ts
export function fn1(a, b) {
return a * b;
}
export function fn2(a, b) {
return a + b;
}
export function fn3(a, b) {
return a - b;
}
The above file, as well are others are bundled as a module and exported in the index.ts as
import * as Helpers from '/myHelpers';
import * as DaoUtils from '/daoUtils';
export { Helpers };
export { DaoUtils };
I consume the above module in my app package.json as:
"helper-utils": "file:/local/app/helper"
Which my angular component uses:
import { Helpers } from 'helper-lib';
export class MyComponent {
constructor() {}
public btnClick(p1, p2) {
let value = Helpers.fn1(p1, p2);
console.log(value);
return value;
}
}
The above works fine and if on btnClick() in the GUI i pass 1 and 2, i see the value of 2 in the console.log.
Unit test:
import { ComponentFixture, TestBed, waitForAsync } from '#angular/core/testing';
import { MyComponent } from './myComponent';
import { Helpers } from 'helper-lib';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({declarations:[MyComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should calc value', () => {
let mySpy = spyOn(Helpers, 'fn1');
let value = component.btnClick(1, 2);
expect(mySpy).toHaveBeenCalled();
});
});
Error:
Error: <spyOn> : fn1 is not declared writable or has no setter

Unfortunately, I am thinking you're coming across this issue: https://stackoverflow.com/a/62935131/7365461.
As TypeScript advanced, most likely the answers linked will not work anymore.
The answer linked and the Github thread linked in the answer explains this.
An unfortunate reality with Angular is that to make testing easier, you should always be thinking about dependency injection (in essence, write re-usable code that can be used everywhere in a service). This way, it becomes easier to test.
The answer I have linked creates a class with a static method that calls the functions and then you can spy on these static methods as well. This way works as well.

If your helpers are pure functions (always return the same result if the same arguments are passed in) then it is perfectly okay not to spy on them and instead expect what the final value should be. This does very heavily tie your tests to the helper, but normally there is only one way to do certain calculations and the helper would have its own spec to prove any complicated logic, where your component would likely only use simpler values.
it('should calc value', () => {
let value = component.btnClick(1, 2);
expect(value).toBe(2);
});
If that isn't the case one other option is to create a new helpers file that looks roughly like
export class Helpers {
static fn1(a: number, b: number) {
return a * b;
}
static fn2(a: number, b: number) {
return a + b;
}
static fn3(a: number, b: number) {
return a - b;
}
}
Now your test can spy on it all it wants
it('should calc value', () => {
const mySpy = spyOn(Helpers, 'fn1').and.callThrough();
const value = component.btnClick(1, 2);
expect(mySpy).toHaveBeenCalled();
expect(value).toBe(2);
});

Related

Memoize object lazily so that first attempt to access it would load it

Is there some npm package for memoizing object lazily, so that the first attempt to access it would load it?
The problem:
// service
class Service {
private readonly pathMap = {
user: process.env.USER_PATH,
post: process.env.POST_PATH,
page: process.env.PAGE_PATH,
}
getPath(entityType: EntityType) {
return this.pathMap[entityType];
}
}
export const service = new Service();
// service.spec.ts
import { service } from './service';
import { loadEnvVars } from '#app/loadEnvVars';
describe('service', () => {
beforeAll(loadEnvVars);
it('should return path', () => {
expect(service.getPath('user')).toBe(process.env.USER_PATH);
expect(service.getPath('post')).toBe(process.env.POST_PATH);
expect(service.getPath('page')).toBe(process.env.PAGE_PATH);
});
});
The tests will fail because the singleton service will load before the loadEnvVars due to the import of the service (before the beforeAll), which means the env vars will be set to undefined in the service pathMap.
The proposed solution:
I know there are several ways to fix it, but IMO the best solution would be to somehow lazy load the pathMap object so that the first attempt to get something from it will actually init the variable's value.
Here's a function I wrote to tackle this (Typescript):
export function lazy<T extends (...args: any[]) => any>(factory: T): ReturnType<T> {
let obj: ReturnType<T> | undefined;
const proxy = new Proxy(
{},
{
get(_, key) {
if (!obj) {
obj = factory();
}
return obj[key];
}
}
);
return proxy as ReturnType<T>;
}
Now the service will look like this instead:
class Service {
private readonly pathMap = lazy(() => ({
user: process.env.USER_PATH,
post: process.env.POST_PATH,
page: process.env.PAGE_PATH,
}));
getPath(entityType: EntityType) {
return this.pathMap[entityType];
}
}
export const service = new Service();
Now the tests will pass.
Note: In this solution lazy returns a read-only object. It can be changed of course.
The question:
Is there some NPM library out there that provides something like that already? Cause if not I think I might publish it myself.

How to spyOn an exported standalone function using javascript jest?

It is a very simple scenario but I've struggled to find an answer for it.
helpers.ts:
export function foo() {
bar();
}
export function bar() {
// do something
}
helpers.spec.ts:
import { foo, bar } from "./helpers";
describe("tests", () => {
it("example test", () => {
const barSpy = // how can i set this up?
foo();
expect(barSpy).toHaveBeenCalled();
});
});
I can't do const spy = jest.spyOn(baz, 'bar'); because I don't have a module/class to put in place of "baz". It is just an exported function.
Edit:
Jest mock inner function has been suggested as a duplicate but unfortunately it doesn't help with my scenario.
Solutions in that question:
Move to separate module: I cannot do this for my scenario. If I am testing every function in my application, this would result in me creating 10s of new files which is not ideal. (To clarify, I think this solution would work but I cannot use it for my scenario. I am already mocking a separate file function successfully in this test file.)
Import the module into itself:
helpers.spec.ts:
import * as helpers from "./helpers";
describe("tests", () => {
it("example test", () => {
const barSpy = jest.spyOn(helpers, 'bar');
foo();
expect(barSpy).toHaveBeenCalled();
});
});
results in:
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
This is the closed solution:
export function bar() {
// do something
}
export function foo() {
exports.bar(); // <-- have to change to exports.bar() instead of bar()
// or this.bar(); would also work.
}
import * as utils from './utils';
describe('tests', () => {
it('example test', () => {
const barSpy = jest.spyOn(utils, 'bar');
utils.foo();
expect(barSpy).toHaveBeenCalled();
});
});
Or take a look this duplicated question

Client-side typescript: how do I remove circular dependencies from the factory method pattern?

I am using the factory method pattern in some of my code. The problem is, some of those instances also use the same factory method pattern. This creates circular dependencies and I can't think of a way of removing them. Let me give an example:
// factoryMethod.ts
import Instance1 from './Instance1';
import Instance2 from './Instance2';
import Instance3 from './Instance3';
import { Instance, InstanceName } from './Instance';
export const getInstanceByName = (
instanceName: InstanceName
): Instance => {
switch (instanceName) {
case 'Instance1':
return Instance1;
case 'Instance2':
return Instance2;
case 'Instance3':
return Instance3;
default:
throw new Error();
}
};
// extremelyHelpfulUtilityFunction.ts
import { getInstanceByName } from './factoryMethod';
export const extremelyHelpfulUtilityFunction = (instanceName: InstanceName): number => {
// Imagine this was an extremely helpful utility function
return getInstanceByName(instanceName).num
}
// Instance.ts
export interface Instance {
doSomething: () => number;
num: number;
}
export type InstanceName = 'Instance1' | 'Instance2' | 'Instance3';
// Instance1.ts
import { extremelyHelpfulUtilityFunction } from './extremelyHelpfulUtilityFunction';
const i: Instance = {
doSomething: (): number => {
return extremelyHelpfulUtilityFunction('Instance2') + extremelyHelpfulUtilityFunction('Instance3'); // circular dependency
},
}
export default i;
// Other instances defined below, you get the idea.
I'm using rollup to turn this into a single JavaScript file, and when I do, it warns me that I have a circular dependency. I want to get rid of this warning. I realize the code will still function, but I don't want the warning there. How can I modify this code so that InstanceX can get InstanceY without it being a circular dependency?
IMO the problem is that extremelyHelpfulUtilityFunction has to know getInstanceByName, while the result of this factory could always be known in advance by the caller and the desired value passed as argument to the helper.
I would propose
// Instance1.ts
const instance1: Instance = {
doSomething: (): number => {
return (new Instance2()).toNum() + (new Instance3()).toNum()
},
}
with toNum defined in Instance.ts and overridden in its subclasses, using the helper but with proper parameters, for example
// Instance2.ts
const instance2: Instance = {
doSomething: ...,
toNum: (): number => {
return extremelyHelpfulUtilityFunction(1234)
}
}
where you would use this.num instead of 1234 if you declared a proper class for Instance2 instead of this object, like
// Instance2.ts
class Instance2 extends Instance {
num = 1234;
doSomething: ...
toNum(): number {
return extremelyHelpfulUtilityFunction(this.num)
}
}
export default new Instance2();

Mock dependency in Jest with TypeScript

When testing a module that has a dependency in a different file and assigning that module to be a jest.mock, TypeScript gives an error that the method mockReturnThisOnce (or any other jest.mock method) does not exist on the dependency, this is because it is previously typed.
What is the proper way to get TypeScript to inherit the types from jest.mock?
Here is a quick example.
Dependency
const myDep = (name: string) => name;
export default myDep;
test.ts
import * as dep from '../depenendency';
jest.mock('../dependency');
it('should do what I need', () => {
//this throws ts error
// Property mockReturnValueOnce does not exist on type (name: string)....
dep.default.mockReturnValueOnce('return')
}
I feel like this is a very common use case and not sure how to properly type this.
You can use type casting and your test.ts should look like this:
import * as dep from '../dependency';
jest.mock('../dependency');
const mockedDependency = <jest.Mock<typeof dep.default>>dep.default;
it('should do what I need', () => {
//this throws ts error
// Property mockReturnValueOnce does not exist on type (name: string)....
mockedDependency.mockReturnValueOnce('return');
});
TS transpiler is not aware that jest.mock('../dependency'); changes type of dep thus you have to use type casting. As imported dep is not a type definition you have to get its type with typeof dep.default.
Here are some other useful patterns I've found during my work with Jest and TS
When imported element is a class then you don't have to use typeof for example:
import { SomeClass } from './SomeClass';
jest.mock('./SomeClass');
const mockedClass = <jest.Mock<SomeClass>>SomeClass;
This solution is also useful when you have to mock some node native modules:
import { existsSync } from 'fs';
jest.mock('fs');
const mockedExistsSync = <jest.Mock<typeof existsSync>>existsSync;
In case you don't want to use jest automatic mock and prefer create manual one
import TestedClass from './TestedClass';
import TestedClassDependency from './TestedClassDependency';
const testedClassDependencyMock = jest.fn<TestedClassDependency>(() => ({
// implementation
}));
it('Should throw an error when calling playSomethingCool', () => {
const testedClass = new TestedClass(testedClassDependencyMock());
});
testedClassDependencyMock() creates mocked object instance
TestedClassDependency can be either class or type or interface
Use the mocked helper
like explained here
// foo.spec.ts
import { foo } from './foo'
jest.mock('./foo')
// here the whole foo var is mocked deeply
const mockedFoo = jest.mocked(foo, true)
test('deep', () => {
// there will be no TS error here, and you'll have completion in modern IDEs
mockedFoo.a.b.c.hello('me')
// same here
expect(mockedFoo.a.b.c.hello.mock.calls).toHaveLength(1)
})
test('direct', () => {
foo.name()
// here only foo.name is mocked (or its methods if it's an object)
expect(jest.mocked(foo.name).mock.calls).toHaveLength(1)
})
There are two solutions tested for TypeScript version 3.x and 4.x, both are casting desired function
1) Use jest.MockedFunction
import * as dep from './dependency';
jest.mock('./dependency');
const mockMyFunction = dep.myFunction as jest.MockedFunction<typeof dep.myFunction>;
2) Use jest.Mock
import * as dep from './dependency';
jest.mock('./dependency');
const mockMyFunction = dep.default as jest.Mock;
There is no difference between these two solutions. The second one is shorter and I would therefore suggest using that one.
Both casting solutions allows to call any jest mock function on mockMyFunction like mockReturnValue or mockResolvedValue
https://jestjs.io/docs/en/mock-function-api.html
mockMyFunction.mockReturnValue('value');
mockMyFunction can be used normally for expect
expect(mockMyFunction).toHaveBeenCalledTimes(1);
I use the pattern from #types/jest/index.d.ts just above the type def for Mocked (line 515):
import { Api } from "../api";
jest.mock("../api");
const myApi: jest.Mocked<Api> = new Api() as any;
myApi.myApiMethod.mockImplementation(() => "test");
Cast as jest.Mock
Simply casting the function to jest.Mock should do the trick:
(dep.default as jest.Mock).mockReturnValueOnce('return')
Use as jest.Mock and nothing else
The most concise way of mocking a module exported as default in ts-jest that I can think of really boils down to casting the module as jest.Mock.
Code:
import myDep from '../dependency' // No `* as` here
jest.mock('../dependency')
it('does what I need', () => {
// Only diff with pure JavaScript is the presence of `as jest.Mock`
(myDep as jest.Mock).mockReturnValueOnce('return')
// Call function that calls the mocked module here
// Notice there's no reference to `.default` below
expect(myDep).toHaveBeenCalled()
})
Benefits:
does not require referring to the default property anywhere in the test code - you reference the actual exported function name instead,
you can use the same technique for mocking named exports,
no * as in the import statement,
no complex casting using the typeof keyword,
no extra dependencies like mocked.
The latest jest allows you to do this very easily with jest.mocked
import * as dep from '../dependency';
jest.mock('../dependency');
const mockedDependency = jest.mocked(dep);
it('should do what I need', () => {
mockedDependency.mockReturnValueOnce('return');
});
Here's what I did with jest#24.8.0 and ts-jest#24.0.2:
source:
class OAuth {
static isLogIn() {
// return true/false;
}
static getOAuthService() {
// ...
}
}
test:
import { OAuth } from '../src/to/the/OAuth'
jest.mock('../src/utils/OAuth', () => ({
OAuth: class {
public static getOAuthService() {
return {
getAuthorizationUrl() {
return '';
}
};
}
}
}));
describe('createMeeting', () => {
test('should call conferenceLoginBuild when not login', () => {
OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
return false;
});
// Other tests
});
});
This is how to mock a non-default class and it's static methods:
jest.mock('../src/to/the/OAuth', () => ({
OAuth: class {
public static getOAuthService() {
return {
getAuthorizationUrl() {
return '';
}
};
}
}
}));
Here should be some type conversion from the type of your class to jest.MockedClass or something like that. But it always ends up with errors. So I just used it directly, and it worked.
test('Some test', () => {
OAuth.isLogIn = jest.fn().mockImplementationOnce(() => {
return false;
});
});
But, if it's a function, you can mock it and do the type conversation.
jest.mock('../src/to/the/Conference', () => ({
conferenceSuccessDataBuild: jest.fn(),
conferenceLoginBuild: jest.fn()
}));
const mockedConferenceLoginBuild = conferenceLoginBuild as
jest.MockedFunction<
typeof conferenceLoginBuild
>;
const mockedConferenceSuccessDataBuild = conferenceSuccessDataBuild as
jest.MockedFunction<
typeof conferenceSuccessDataBuild
>;
As of Jest 24.9.0 here is how you can mock and correctly type both your Class/Object/function and Jest properties.
jest.MockedFunction
jest.MockedClass
What we would like for a typed mock is that the mocked object type contains the union of the mocked object type and the type of Jest mocks.
import foo from 'foo';
jest.mock('foo');
const mockedFoo = foo as jest.MockedFunction<typeof foo>;
// or: const mockedFooClass = foo as jest.MockedClass<typeof FooClass>;
mockedFoo.mockResolvedValue('mockResult');
// Or:
(mockedFoo.getSomething as jest.MockedFunction<typeof mockedFoo.getSomething>).mockResolvedValue('mockResult');
As you can see, you can either manually cast what you need or you'll need something to traverse all foo's properties/methods to type/cast everything.
To do that (deep mock types) you can use jest.mocked() introduced in Jest 27.4.0
import foo from 'foo';
jest.mock('foo');
const mockedFoo = jest.mocked(foo, true);
mockedFoo.mockImplementation() // correctly typed
mockedFoo.getSomething.mockImplementation() // also correctly typed
I have found this in #types/jest:
/**
* Wrap a function with mock definitions
*
* #example
*
* import { myFunction } from "./library";
* jest.mock("./library");
*
* const mockMyFunction = myFunction as jest.MockedFunction<typeof myFunction>;
* expect(mockMyFunction.mock.calls[0][0]).toBe(42);
*/
Note: When you do const mockMyFunction = myFunction and then something like mockFunction.mockReturnValue('foo'), you're a changing myFunction as well.
Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jest/index.d.ts#L1089
The top rated solution by Artur Górski does not work with the last TS and Jest.
Use MockedClass
import SoundPlayer from '../sound-player';
jest.mock('../sound-player'); // SoundPlayer is now a mock constructor
const SoundPlayerMock = SoundPlayer as jest.MockedClass<typeof SoundPlayer>;
A recent library solves this problem with a babel plugin: https://github.com/userlike/joke
Example:
import { mock, mockSome } from 'userlike/joke';
const dep = mock(import('./dependency'));
// You can partially mock a module too, completely typesafe!
// thisIsAMock has mock related methods
// thisIsReal does not have mock related methods
const { thisIsAMock, thisIsReal } = mockSome(import('./dependency2'), () => ({
thisIsAMock: jest.fn()
}));
it('should do what I need', () => {
dep.mockReturnValueOnce('return');
}
Be aware that dep and mockReturnValueOnce are fully type safe. On top, tsserver is aware that depencency was imported and was assigned to dep so all automatic refactorings that tsserver supports will work too.
Note: I maintain the library.
This is ugly, and in fact getting away from this ugliness is why I even looked at this question, but to get strong typing from a module mock, you can do something like this:
const myDep = (require('./dependency') as import('./__mocks__/dependency')).default;
jest.mock('./dependency');
Make sure you require './dependency' rather than the mock directly, or you will get two different instantiations.
For me this was enough:
let itemQ: queueItemType
jest.mock('../dependency/queue', () => {
return {
add: async (item: queueItemType, ..._args: any) => {
// then we can use the item that would be pushed to the queue in our tests
itemQ = item
return new Promise(resolve => {
resolve('Mocked')
})
},
}
})
Then, whenever the add method is called it will execute this code above instead of pushing it to the queue, in this case.
With TypeScript 2.8 we can do like this with ReturnType:
import * as dep from "./depenendency"
jest.mock("./dependency")
const mockedDependency = <jest.Mock<ReturnType<typeof dep.default>>>dep.default
it("should do what I need", () => {
mockedDependency.mockReturnValueOnce("return")
})

ES6 Singleton vs Instantiating a Class once

I see patterns which make use of a singleton pattern using ES6 classes and I am wondering why I would use them as opposed to just instantiating the class at the bottom of the file and exporting the instance. Is there some kind of negative drawback to doing this? For example:
ES6 Exporting Instance:
import Constants from '../constants';
class _API {
constructor() {
this.url = Constants.API_URL;
}
getCities() {
return fetch(this.url, { method: 'get' })
.then(response => response.json());
}
}
const API = new _API();
export default API;
Usage:
import API from './services/api-service'
What is the difference from using the following Singleton pattern? Are there any reasons for using one from the other? Im actually more curious to know if the first example I gave can have issues that I am not aware of.
Singleton Pattern:
import Constants from '../constants';
let instance = null;
class API {
constructor() {
if(!instance){
instance = this;
}
this.url = Constants.API_URL;
return instance;
}
getCities() {
return fetch(this.url, { method: 'get' })
.then(response => response.json());
}
}
export default API;
Usage:
import API from './services/api-service';
let api = new API()
I would recommend neither. This is totally overcomplicated. If you only need one object, do not use the class syntax! Just go for
import Constants from '../constants';
export default {
url: Constants.API_URL,
getCities() {
return fetch(this.url, { method: 'get' }).then(response => response.json());
}
};
import API from './services/api-service'
or even simpler
import Constants from '../constants';
export const url = Constants.API_URL;
export function getCities() {
return fetch(url, { method: 'get' }).then(response => response.json());
}
import * as API from './services/api-service'
The difference is if you want to test things.
Say you have api.spec.js test file. And that your API thingy has one dependency, like those Constants.
Specifically, constructor in both your versions takes one parameter, your Constants import.
So your constructor looks like this:
class API {
constructor(constants) {
this.API_URL = constants.API_URL;
}
...
}
// single-instance method first
import API from './api';
describe('Single Instance', () => {
it('should take Constants as parameter', () => {
const mockConstants = {
API_URL: "fake_url"
}
const api = new API(mockConstants); // all good, you provided mock here.
});
});
Now, with exporting instance, there's no mocking.
import API from './api';
describe('Singleton', () => {
it('should let us mock the constants somehow', () => {
const mockConstants = {
API_URL: "fake_url"
}
// erm... now what?
});
});
With instantiated object exported, you can't (easily and sanely) change its behavior.
Both are different ways.
Exporting a class like as below
const APIobj = new _API();
export default APIobj; //shortcut=> export new _API()
and then importing like as below in multiple files would point to same instance and a way of creating Singleton pattern.
import APIobj from './services/api-service'
Whereas the other way of exporting the class directly is not singleton as in the file where we are importing we need to new up the class and this will create a separate instance for each newing up
Exporting class only:
export default API;
Importing class and newing up
import API from './services/api-service';
let api = new API()
Another reason to use Singleton Pattern is in some frameworks (like Polymer 1.0) you can't use export syntax.
That's why second option (Singleton pattern) is more useful, for me.
Hope it helps.
Maybe I'm late, because this question is written in 2018, but it still appear in the top of result page when search for js singleton class and I think that it still not have the right answer even if the others ways works. but don't create a class instance.
And this is my way to create a JS singleton class:
class TestClass {
static getInstance(dev = true) {
if (!TestClass.instance) {
console.log('Creating new instance');
Object.defineProperty(TestClass, 'instance', {
value: new TestClass(dev),
writable : false,
enumerable : true,
configurable : false
});
} else {
console.log('Instance already exist');
}
return TestClass.instance;
}
random;
constructor() {
this.random = Math.floor(Math.random() * 99999);
}
}
const instance1 = TestClass.getInstance();
console.log(`The value of random var of instance1 is: ${instance1.random}`);
const instance2 = TestClass.getInstance();
console.log(`The value of random var of instance2 is: ${instance2.random}`);
And this is the result of execution of this code.
Creating new instance
The value of random var of instance1 is: 14929
Instance already exist
The value of random var of instance2 is: 14929
Hope this can help someone

Categories

Resources