I'm stuck trying to mock a class that is passed in through constructor and all the examples I've encountered so far have initialized dependencies inside constructors.
From my understanding jest replaces such dependencies with its overridden constructor but because I'm passing in the dependencies myself I'm in need of a passable instance when initialising UnderTest.
For what it's worth I'm ideally looking for a Mockito-ish behaviour, something along the lines of.
const mockedDependency = ???
const underTest = new UnderTest(mockedDependency)
...
Proceed to write tests for underTest
This is the code I'm trying to test. Please assume that MockedDependency has its own dependencies, passed in to constructor as well.
export default class UnderTest {
private mockedDependency : MockedDependency
constructor(mockedDependency: MockedDependency) {
this.mockedDependency = mockedDependency
}
public methodUnderTest(parameter: string) {
const mockedResult = this.mockedDependency.returnSomething(parameter)
return this.doSomethingElse(mockedResult)
}
public methodUnderTest2(parameter1: string, parameter2: string) {
const mockedResult = this.mockedDependency.returnSomething2(parameter1, parameter2)
return this.doSomethingElse(mockedResult)
}
private doSomethingElse(mockedResult: string) {
return mockedResult
}
}
How would you go about unit testing UnderTest class?
Bonus points for a way to set the result of mockedDependency methods either in each test or by input.
Edit:
A possible solution to those who stumble upon the same issue:
It's possible to cast variable as the desired object and override the methods like so:
const mockedDependency = {
returnSomething(parameter: string) {
return parameter
}
} as MockedDependency
const underTest = new UnderTest(mockedDependency)
It's far from perfect but will do for simpler cases.
Thanks in advance!
Here is the solution:
UnderTest.ts:
export interface MockedDependency {
returnSomething(...args: any[]): any;
returnSomething2(...args: any[]): any;
}
export default class UnderTest {
private mockedDependency: MockedDependency;
constructor(mockedDependency: MockedDependency) {
this.mockedDependency = mockedDependency;
}
public methodUnderTest(parameter: string) {
const mockedResult = this.mockedDependency.returnSomething(parameter);
return this.doSomethingElse(mockedResult);
}
public methodUnderTest2(parameter1: string, parameter2: string) {
const mockedResult = this.mockedDependency.returnSomething2(parameter1, parameter2);
return this.doSomethingElse(mockedResult);
}
private doSomethingElse(mockedResult: string) {
return mockedResult;
}
}
UnderTest.spec.ts:
import UnderTest, { MockedDependency } from './UnderTest';
const mockedDeps: jest.Mocked<MockedDependency> = {
returnSomething: jest.fn(),
returnSomething2: jest.fn()
};
const underTest = new UnderTest(mockedDeps);
describe('UnderTest', () => {
afterEach(() => {
jest.resetAllMocks();
});
describe('#methodUnderTest', () => {
it('should correctly', () => {
mockedDeps.returnSomething.mockReturnValueOnce('mocked result');
const actualValue = underTest.methodUnderTest('1');
expect(actualValue).toBe('mocked result');
expect(mockedDeps.returnSomething).toBeCalledWith('1');
});
});
describe('#methodUnderTest2', () => {
it('should correctly', () => {
mockedDeps.returnSomething2.mockReturnValueOnce('mocked result');
const actualValue = underTest.methodUnderTest2('2', '3');
expect(actualValue).toBe('mocked result');
expect(mockedDeps.returnSomething2).toBeCalledWith('2', '3');
});
});
});
Unit test result with 100% coverage report:
PASS src/stackoverflow/55966013/UnderTest.spec.ts
UnderTest
#methodUnderTest
✓ should correctly (5ms)
#methodUnderTest2
✓ should correctly (1ms)
--------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
UnderTest.ts | 100 | 100 | 100 | 100 | |
--------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.597s, estimated 8s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/55966013
Related
I just trying to implement a hooks for fetching data from window by useState, I'm tripping up over generics and code as below
interface ApplyData {
//...
}
interface ApplyRouterData {
//...
}
export const useApplyRouterData = () => useWindowData<ApplyRouterData>();
export const useApplyData = () => useWindowData<ApplyData>();
export const useWindowData = <T extends ApplyData | ApplyRouterData>() => {
return useState<T>(() => {
return window.data;
});
};
I also declare type for window.data like this
declare global {
interface Window {
data: ApplyRouterData | ApplyData;
}
}
but I got complier error om my hook
'T' could be instantiated with an arbitrary type which could be unrelated to 'IApplyResp | IApplyRouterResp'.
the type of window.data and generics are same as IApplyResp | IApplyRouterResp in my view, why? Thanks for you answer.
I have an Animal class as follows
Animal.js
export default class Animal {
constructor(type) {
this.type = type
}
getAnimalSound(animal) {
if (animal && animal.type == 'dog') return 'woof'
if (animal && animal.type == 'cat') return 'meow'
}
}
I make a zoo module which has a method for getAnimalSound() as follows
zoo.js
import Animal from './Animal'
export default function getAnimalSound(type) {
let animal = new Animal(type)
let animalSound = animal.getAnimalSound(animal)
return animalSound
}
Now how do i make unit testing for zoo module?
zoo.test.js
import sinon from 'sinon'
import Animal from './Animal'
import getAnimalSound from './zoo'
let animalStub = sinon.createStubInstance(Animal)
let a = animalStub.getAnimalSound.returns('woof')
let sound = getAnimalSound('cat')
console.log(sound)
So the problem is that the 'new' has no effect by the way i have stubbed in test.js
Can i achieve this?
Regards
Bobu P
You could use Link Seams to mock your ./animal.js module and Animal class.
E.g.
animal.ts:
export default class Animal {
type: any;
constructor(type) {
this.type = type;
}
getAnimalSound(animal) {
if (animal && animal.type == 'dog') return 'woof';
if (animal && animal.type == 'cat') return 'meow';
}
}
zoo.ts:
import Animal from './animal';
export default function getAnimalSound(type) {
let animal = new Animal(type);
let animalSound = animal.getAnimalSound(animal);
return animalSound;
}
zoo.test.ts:
import sinon from 'sinon';
import proxyquire from 'proxyquire';
import { expect } from 'chai';
describe('61716637', () => {
it('should pass', () => {
const animalInstanceStub = {
getAnimalSound: sinon.stub().returns('stubbed value'),
};
const AnimalStub = sinon.stub().returns(animalInstanceStub);
const getAnimalSound = proxyquire('./zoo', {
'./animal': { default: AnimalStub },
}).default;
const actual = getAnimalSound('bird');
expect(actual).to.be.equal('stubbed value');
sinon.assert.calledWith(AnimalStub, 'bird');
sinon.assert.calledWith(animalInstanceStub.getAnimalSound, animalInstanceStub);
});
});
unit test results with coverage report:
61716637
✓ should pass (2242ms)
1 passing (2s)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 54.55 | 0 | 33.33 | 66.67 |
animal.ts | 16.67 | 0 | 0 | 25 | 4-8
zoo.ts | 100 | 100 | 100 | 100 |
-----------|---------|----------|---------|---------|-------------------
I have two classes that simulate a simple sum operation.
import SumProcessor from "./SumProcessor";
class Calculator {
constructor(private _processor: SumProcessor) { }
sum(a: number, b: number): number {
return this._processor.sum(a, b)
}
}
export default Calculator
And the operation processor.
class SumProcessor {
sum(a: number, b: number): number {
return a + b
}
static log() {
console.log('houston...')
}
}
export default SumProcessor
I'm tryng to mock the class SumProcessor to write the following unit test using jest+ts-jest.
import Calculator from "./Calculator"
import SumProcessor from "./SumProcessor"
import { mocked } from "ts-jest/utils"
jest.mock('./SumProcessor')
describe('Calculator', () => {
it('test sum', () => {
const SomadorMock = <jest.Mock>(SumProcessor)
SomadorMock.mockImplementation(() => {
return {
sum: () => 2
}
})
const somador = new SomadorMock()
const calc = new Calculator(somador)
expect(calc.sum(1, 1)).toBe(2)
})
})
When the static method is present in class SumProcessor, the mock code const SomadorMock = (SumProcessor) indicates the following compilation error:
TS2345: Argument of type '() => jest.Mock<any, any>' is not assignable to parameter of type '(values?: object, option
s?: BuildOptions) => SumOperator'.
Type 'Mock<any, any>' is missing the following properties from type 'SumOperator...
If the static method is removed from SumProcessor class, everything work's fine.
Can anybody help?
since you have already mocked the SumProcessor class with jest.mock('./SumProcessor'); you can just add a spy to the method you would like to mock, for an example:
jest.spyOn(SumProcessor.prototype, 'sum').mockImplementation(() => 2);
this way your test class would look something like this:
import Calculator from "./Calculator"
import SumProcessor from "./SumProcessor"
jest.mock('./SumProcessor')
describe('Calculator', () => {
it('test sum', () => {
jest.spyOn(SumProcessor.prototype, 'sum').mockImplementation(() => 2);
const somador = new SumProcessor();
const calc = new Calculator(somador)
expect(calc.sum(1, 1)).toBe(2)
})
})
much simpler, right?
I am trying to write a test case using Jest. How can I write the test case for below snippets? A is a separate file and Three is a different file. I need to write the test cases as separate files as like unit test cases.
I am stuck in writing the static methods calling and mocking the multiple inputs. See below what I have tried also.
const one = require('../one');
const two = require('../two');
const three = require('../three');
class A {
public static checkTesting(param) {
switch (param) {
case 'one':
return one;
case 'two':
return two;
default:
return three;
}
}
constructor(param) {
this.testing = A.checkTesting(param);
}
}
module.exports = A;
const multiple = require('../multiple')(module);
const config = require('../config');
class Three {
public static sampleTestingWrite() {
return {
b: param => multiple[config.access](param)
};
}
constructor() {
this.sampleTesting = Three.sampleTestingWrite();
}
}
module.exports = Three;
A.test.js:
const One = require('../one');
const Two = require('../two');
const Three = require('../three');
const A = require('..');
jest.mock('../one');
jest.mock('../two');
jest.mock('../three');
describe('A test cases', () => {
test('should initiate the constructor', () => {
const mockStaticFunction = jest.fn();
mockStaticFunction.mockReturnValue('returns an object which does something on Multiple');
const MockA = new A('one');
console.log(MockA);
Console.mockImplementation(() => ({}));
console.log(logMedium);
expect(Console).toHaveBeenCalledTimes(1);
});
});
If you want to test if the constructor of A is called correctly. There is no need to mock one, two, three modules. Only you need to mock/spyOn is
checkTesting static method of A.
Here is the solution, I use typescript
import one from './one';
import two from './two';
import three from './three';
class A {
public static checkTesting(param) {
switch (param) {
case 'one':
return one;
case 'two':
return two;
default:
return three;
}
}
private testing;
constructor(param) {
this.testing = A.checkTesting(param);
}
}
export { A };
Unit test:
import { A } from './a';
describe('A', () => {
describe('checkTesting', () => {
it('should initiate the constructor', () => {
jest.spyOn(A, 'checkTesting').mockReturnValue({ name: 'never mind' });
const param = 'one';
const a = new A(param);
expect(a).toBeInstanceOf(A);
expect(A.checkTesting).toBeCalledWith(param);
});
});
});
Unit test result with coverage:
PASS src/mock-function/57624975/a.spec.ts
A
checkTesting
✓ t1 (8ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 73.33 | 0 | 66.67 | 73.33 | |
a.ts | 66.67 | 0 | 66.67 | 66.67 | 7,9,11,13 |
one.ts | 100 | 100 | 100 | 100 | |
three.ts | 100 | 100 | 100 | 100 | |
two.ts | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.649s, estimated 3s
I am using the following approach to memoize a TypeScript getter using a decorator but wanted to know if there is a better way. I am using the popular memoizee package from npm as follows:
import { memoize } from '#app/decorators/memoize'
export class MyComponent {
#memoize()
private static memoizeEyeSrc(clickCount, maxEyeClickCount, botEyesDir) {
return clickCount < maxEyeClickCount ? botEyesDir + '/bot-eye-tiny.png' : botEyesDir + '/bot-eye-black-tiny.png'
}
get leftEyeSrc() {
return MyComponent.memoizeEyeSrc(this.eyes.left.clickCount, this.maxEyeClickCount, this.botEyesDir)
}
}
AND the memoize decorator is:
// decorated method must be pure
import * as memoizee from 'memoizee'
export const memoize = (): MethodDecorator => {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const func = descriptor.value
descriptor.value = memoizee(func)
return descriptor
}
}
Is there a way to do this without using two separate functions in MyComponent and to add the decorator directly to the TypeScript getter instead?
One consideration here is that the decorated function must be pure (in this scenario) but feel free to ignore that if you have an answer that doesn't satisfy this as I have a general interest in how to approach this problem.
The decorator can be extended to support both prototype methods and getters:
export const memoize = (): MethodDecorator => {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
if ('value' in descriptor) {
const func = descriptor.value;
descriptor.value = memoizee(func);
} else if ('get' in descriptor) {
const func = descriptor.get;
descriptor.get = memoizee(func);
}
return descriptor;
}
}
And be used directly on a getter:
#memoize()
get leftEyeSrc() {
...
}
Based on #estus answer, this is what I finally came up with:
#memoize(['this.eyes.left.clickCount'])
get leftEyeSrc() {
return this.eyes.left.clickCount < this.maxEyeClickCount ? this.botEyesDir + '/bot-eye-tiny.png' : this.botEyesDir + '/bot-eye-black-tiny.png'
}
And the memoize decorator is:
// decorated method must be pure when not applied to a getter
import { get } from 'lodash'
import * as memoizee from 'memoizee'
// noinspection JSUnusedGlobalSymbols
const options = {
normalizer(args) {
return args[0]
}
}
const memoizedFuncs = {}
export const memoize = (props: string[] = []): MethodDecorator => {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
props = props.map(prop => prop.replace(/^this\./, ''))
if ('value' in descriptor) {
const valueFunc = descriptor.value
descriptor.value = memoizee(valueFunc)
} else if ('get' in descriptor) {
const getFunc = descriptor.get
// args is used here solely for determining the memoize cache - see the options object
memoizedFuncs[propertyKey] = memoizee((args: string[], that) => {
const func = getFunc.bind(that)
return func()
}, options)
descriptor.get = function() {
const args: string[] = props.map(prop => get(this, prop))
return memoizedFuncs[propertyKey](args, this)
}
}
return descriptor
}
}
This allows for an array of strings to be passed in which determine which properties will be used for the memoize cache (in this case only 1 prop - clickCount - is variable, the other 2 are constant).
The memoizee options state that only the first array arg to memoizee((args: string[], that) => {...}) is to be used for memoization purposes.
Still trying to get my head around how beautiful this code is! Must have been having a good day. Thanks to Yeshua my friend and Saviour :)