It's not possible to mock classes with static methods using jest and ts-jest - node.js

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?

Related

TS decorator to wrap function definition in try catch

Is it possible to use TS decorator to wrap a function definition into a try-catch block. I don't want to use try-catch in every function so I was thinking maybe decorators can help.
For example
function examleFn(errorWrapper: any) {
try{
// some code
} catch (err) {
errorWrapper(err)
}
}
Something like this can be done in a decorator so that it can be used for other functions too.
No, you cannot decorate functions.
TypeScript's implementation of decorators can only apply to classes, class methods, class accessors, class properties, or class method parameters. The relevant proposal for JavaScript decorators (at Stage 3 of the TC39 Process as of today, 2022-07-21) also does not allow for decorating functions.
Function decorators are mentioned as possible extensions to the decorator proposal, but are not currently part of any proposal for either TypeScript or JavaScript.
You can, of course, call a decorator-like function on another function, but this is just a higher-order function and not a decorator per se, and it won't affect the original function declaration:
const makeErrorWrapper = <T,>(errorHandler: (err: any) => T) =>
<A extends any[], R>(fn: (...a: A) => R) =>
(...a: A): R | T => {
try {
return fn(...a);
} catch (err) {
return errorHandler(err);
}
};
The makeErrorWrapper function takes an error handler and returns a new function that wraps other functions with that error handler:
const errToUndefined = makeErrorWrapper(err => undefined);
So now errToUndefined is a function wrapper. Let's say we have the following function which throws errors:
function foo(x: string) {
if (x.length > 3) throw new Error("THAT STRING IS TOO LONG");
return x.length;
}
// function foo(x: string): number
If you call it directly, you can get runtime errors:
console.log(foo("abc")); // 3
console.log(foo("abcde")); // 💥 THAT STRING IS TOO LONG
Instead you can wrap it:
const wrappedFoo = errToUndefined(foo);
// const wrappedFoo: (x: string) => number | undefined
Now wrappedFoo is a new function that behaves like foo and takes the same parameter list as foo, but returns number | undefined instead of just number:
console.log(wrappedFoo("abc")) // 3
console.log(wrappedFoo("abcde")) // undefined
Playground link to code
maybe this can help you, it took me a long time to do it, but here it is
function Execpetion (methodName: string) {
return (target: any, nameMethod: string, descriptor: PropertyDescriptor) => {
const originalMethod = descriptor.value
descriptor.value = async function (...args: any[]) {
try {
const executionMethod = await originalMethod.apply(this, args)
return executionMethod
} catch (error) {
return errorWrapper(error as Error)
}
}
}
}
in your class
class TestController {
#Execpetion('TestController')
public async handler (teste: any) {
return {
statusCode: 200,
data: 'nothing'
}
}
}
with the parent function, you can modify and add to receive the errorPersonalized and instantiated type parameter... and on the return put it

'..' could be instantiated with an arbitrary type which could be unrelated to '...'

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.

Typescript: Generic type of method params to match type of callback function params

I'm trying to make a class that accepts a function in the constructor. The function can have arguments of any type. Then I want to put a method on the class that accepts that same arguments as function parameter, as it will be a wrapper around this callback. Here's a simplified example to show what I'm trying to do
interface Options<T> {
callbackFn(...x: any[]) => Promise<T>
}
class ExampleClass<T> {
private options: Options<T>;
result: T;
constructor(options: Options<T>) {
this.options = options;
}
async wrapperFn(...x: any[]) {
// Do some stuff before the callback
this.result = await this.options.callbackFn(x)
// Do some stuff after
}
}
const example = new ExampleClass<string>({
callbackFn: (a: string, b:string) => new Promise((res) => {
res(a + b);
})
});
example.wrapperFn("foo", "bar")
This is basically the way I have it now, and it works but it obviously doesn't enforce the types of the params of wrapperFn which isn't ideal. Is there any way to do something like this?
If you want the compiler to keep track of both the callback return type and the callback argument list type, then you'll want Options to be generic in both the return type (you called it T but I'll call it R for "return") and the argument list type (I'll call it A for "arguments"):
interface Options<A extends any[], R> {
callbackFn(...x: A): Promise<R>
}
Now you can just use A anywhere you were using any[] before, and you'll get stronger typing. This also implies that ExampleClass needs to be generic in A and R too:
class ExampleClass<A extends any[], R> {
private options: Options<A, R>;
result?: R;
constructor(options: Options<A, R>) {
this.options = options;
}
async wrapperFn(...x: A) {
// Do some stuff before the callback
this.result = await this.options.callbackFn(...x)
// Do some stuff after
}
}
Let's test it out:
const example = new ExampleClass({
callbackFn: (a: string, b: string) => new Promise<string>((res) => {
res(a + b);
})
});
// const example: ExampleClass<[a: string, b: string], string>
example.wrapperFn("foo", "bar") // okay
example.wrapperFn("foo", 123); // error!
// --------------------> ~~~
// Argument of type 'number' is not assignable to parameter of type 'string'.
Looks good.
Playground link to code

Dynamic Dependency injection with Typescript using tsyringe

I am trying to build and example to understand how the DI framework/library works, but i am encountering some problems.
I have this interface with two possible implementations:
export interface Operation {
calculate(a: number, b: number): number;
}
sub.ts
import { Operation } from "./operation.interface";
export class Sub implements Operation {
calculate(a: number, b: number): number {
return Math.abs(a - b);
}
}
sum.ts
import { Operation } from "./operation.interface";
export class Sum implements Operation {
calculate(a: number, b: number): number {
return a + b;
}
}
calculator.ts
import { Operation } from "./operation.interface";
import {injectable, inject} from "tsyringe";
#injectable()
export class Calculator {
constructor(#inject("Operation") private operation?: Operation){}
operate(a: number, b: number): number {
return this.operation.calculate(a, b);
}
}
index.ts
import "reflect-metadata";
import { container } from "tsyringe";
import { Calculator } from "./classes/calculator";
import { Sub } from "./classes/sub";
import { Sum } from "./classes/sum";
container.register("Operation", {
useClass: Sum
});
container.register("OperationSub", {
useClass: Sub
});
const calculatorSum = container.resolve(Calculator);
const result = calculatorSum.operate(4,6);
console.log(result);
// const calculatorSub = ???
is there a way where I could have two calculators with different behaviours or am I doing it completely wrong?
Since OperationSub isn’t used anywhere, it cannot affect injected Operation value.
Calculators with different dependency sets should be represented with multiple containers. Summing calculator can be considered a default implementation and use root container, or both implementations can be represented by children containers while root container remains abstract.
// common deps are registered on `container`
const sumContainer = container.createChildContainer();
const subContainer = container.createChildContainer();
sumContainer.register("Operation", { useClass: Sum });
subContainer.register("Operation", { useClass: Sub });
const calculatorSum = sumContainer.resolve(Calculator);
const calculatorSub = subContainer.resolve(Calculator);

how to memoize a TypeScript getter

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 :)

Resources