How to mock class constructor and static function with jest - javascript

I want to mock simultaneously both my default exported class constructor and a static function of the said class that instantiate and returns an instance of the said class(singleton). How can I do it with Jest. Here is a code sample:
export default class MyClass {
private static instance: MyClass;
private data: any;
constructor() {
this.data = ...DefaultData...;
}
public static getInstance(): MyClass {
if (!MyClass.instance) {
MyClass.instance = new MyClass();
}
return MyClass.instance;
}
public setData(newData){
this.data = newData;
}
public methodA(){...doSomethingWith_this.data}
}
Also as requested heres an example of my test where I only have mocked the 'getInstance' method.
import MyClass from 'path/to/MyClass';
jest.mock('path/to/MyClass', () => {
const instance = {
methodA: jest.fn(),
setData: jest.fn()
};
return {
__esModule: true,
default: {
getInstance: () => instance
}
};
});
describe('SystemUnderTest', () => {
it('test for in code instantiation', ()=>{});
it('test for singleton instance', ()=>{});
})
Finally the error I get when running the tests is this
_MyClass.default is not a constructor

Change this to MyClass. A static variable is a class property that is used in a class and not on the instance of the class.
export default class MyClass {
private static instance: MyClass;
constructor() {}
public static getInstance(): MyClass {
if (!MyClass.instance) {
MyClass.instance = new MyClass();
}
return MyClass.instance;
}
}
This is how a singleton works based on your code example. You do not have any example from Jest therefore I must refer to https://stackoverflow.com/help/how-to-ask

Related

How can I inherit class in javascript and use static method?

I have this simple js service that I want to use as a static method. Right now here's my code:
class BaseService {
constructor() {
this.baseUrl = '';
}
list(params) {
return axios.get(`${this.baseUrl}`, { params }).then((response) => response.data);
}
//(...)
}
class DimensionService extends BaseService {
constructor() {
super();
this.baseUrl = '/api/dimensions';
}
}
//usage
let service = new DimensionService();
service.list();
and I would rather use it like that:
DimensionService.list()
the problem is I cannot inherit baseUrl, because in the static method the constructor is never called. What can I do?

'this' returns undefined in extended Classes in Javascript

I am trying to run the code below, but it is not working. I think this is a scope problem, but I'm not sure how to fix this.
import CommonController from './CommonController';
import CategoryService from './category/Service.js';
class CategoryController extends CommonController {
constructor() {
super(CategoryService);
}
}
export default new CategoryController();
// ===================CommonController==========================
export default class CommonController {
constructor(service) {
this.service = service;
}
async get () {
console.log(this); // it returns undefined
}
}
// ===================CategoryService==========================
import Category from './Category'
import dto from './dto'
class CategoryService extends CommonService {
constructor() {
super(Category, dto);
}
}
export default new CategoryService();
// ===================CommonService==========================
export default class CommonService {
constructor(model, dto) {
this.model = model;
this.dto = dto;
}
}
if a run:
import CategoryController from './CategoryController';
CategoryController.get()
the console.log in CommonController get function will print undefined
Am I doing something wrong?
The issue is that you are calling get() on the class itself, instead of calling it on an instance of the class. Try creating an instance of CategoryController, like so:
cc = new CategoryController();
Then, you should be able to call:
cc.get();
Demo in the code below (same as yours, just slightly modified to reflect my point).
// ===================CommonController==========================
class CommonController {
constructor(service) {
this.service = service;
}
async get () {
console.log(this); // it returns undefined
}
}
// ===================CommonService==========================
class CommonService {
constructor(model, dto) {
this.model = model;
this.dto = dto;
}
}
// ===================CategoryService==========================
class CategoryService extends CommonService {
constructor() {
super(Category, dto);
}
}
class CategoryController extends CommonController {
constructor() {
super(CategoryService);
}
}
cs = new CategoryController();
cs.get();

How to pass a newable `this` to a static method from a static method?

How do I pass a newable this to a static method from a static method?
TypeScript Playground
abstract class Model {
public static convert<T extends Model>(model: new () => T, data: any | any[]) {
return new model()
}
public static all<T extends Model>(): Promise<[]> {
let items: any[] = []
return Model.convert(this, items)
// ^--- Error is here
}
}
The resulting JavaScript works, and I get an instance of A, however typescript complains with this error:
Argument of type 'typeof Model' is not assignable to parameter of type 'new () => Model'.
class Model {
static convert(model, data) { return new model() }
static all() {
let items = [];
return Model.convert(this, items);
}
}
class A extends Model { }
console.log(A.all(), A.all().constructor.name);
This would normally work if Model were not abstract. If Model was not abstract this (which is typed as typeof Model) would have the new () => Model constructor, but since it is abstract, it does not have a callable constructor.
The simple solution is to add an annotation for this in the static method:
abstract class Model {
public static convert<T extends Model>(model: new () => T, data: any | any[]): T[] { return null!; }
public static all<T extends Model>(this: new () => T): Promise<T[]> {
let items: any[] = []
return Promise.resolve(Model.convert(this, items));
}
}
class A extends Model { }
console.log(Model.all()) // err since Model does not have a ctor
console.log(A.all(), A.all().constructor.name)
Play
This will make all un-callable for Model which is probably what you want anyway.
Edit
Since in your actual use case you want to call static methods from other static methods we need a slightly more complex annotation for this. The previous version just keeps the constructor signature and none of the static members. You need the static members represented by typeof Model in the annotation. The simplest way to do this is to add typeof Model in an intersection with the constructor signature we previously defined ((new () => T)) basically adding a constructor to the abstract class.
type NonAbstracModel<T extends Model> = (new () => T) & typeof Model
abstract class Model {
public static convert<T extends Model>(model: NonAbstracModel<T>, data: any | any[]): T { return new model() }
public static all<T extends Model>(this: NonAbstracModel<T>): T {
let items: any[] = []
return Model.convert(this, items)
}
public static find<T extends Model>(this: NonAbstracModel<T>, primaryKey: string | object) { }
public static firstOrFail<T extends Model>(this:NonAbstracModel<T>) {
this.find('abc')
}
}
class A extends Model {}
console.log(A.all(), A.all().constructor.name)

Pass class as generic parameter Typescript

im trying to instance a class passed as parameter to another, I have this in one file, ImportedClass.ts:
export default class ImportedClass {
public constructor(something: any) {
}
public async exampleMethod() {
return "hey";
}
}
And this in another, InstanceClass.ts:
interface GenericInterface<T> {
new(something: any): T;
}
export default class InstanceClass <T> {
private c: GenericInterface<T>;
public constructor(c: T) {
}
async work() {
const instanceTry = new this.c("hello");
instanceTry.exampleMethod();
}
}
And this in another, ClassCaller.ts: <--EDITED-->
import ImportedClass from './ImportedClass';
import ImportedClass from './InstanceClass';
const simulator = new InstanceClass <ImportedClass>(ImportedClass);
Then when I call it like this:
simulator.work();
It throw this error:
error TS2339: Property 'exampleMethod' does not exist on type 'T'.
Any help is welcome, thanks.
If T must have a method named exampleMethod you must include this in the constraint for T on Simulator to be able to use it inside Simulator:
export class ImportedClass {
public constructor(something: any) {
}
public async exampleMethod() {
return "hey";
}
}
interface GenericInterface<T> {
new(something: any): T;
}
export class Simulator<T extends { exampleMethod(): Promise<string> }> {
public constructor(private c: GenericInterface<T>) {
}
async work() {
const instanceTry = new this.c("hello");
await instanceTry.exampleMethod();
}
}
const simulator = new Simulator(ImportedClass);
simulator.work()
Playground link
There were other small issues that needed to be fixed to make the snippet above work, but that is the mai issue.

Execute method by decorator

I would like to execute a function by a decorator with the current component this after angular run DI.
An example:
If execute on decorator, I don't have translateService on this
export const TesteDecorator = (actionName): any => {
return (target?: any, propertyKey?: string): void => {
Observable.timer(5000).subscribe(() => {
target[propertyKey](); // when i execute here not have translateService on (this)
});
};
};
But if execute on constructor have the translateService .
#Component({
...
})
export class TesteComponent {
constructor(
private translateService: TranslateService
) {
Observable.timer(1000).subscribe(() => {
this.teste(); // when i execute here translateService is on the (this)
});
}
#TesteDecorator('ACTION')
teste() {
console.log(this);
}
}
Can someone help me?
The problem is that the decorator is executed on class declaration and target is not an instance of the class but it's prototype, so it will not contain any fields.
One approach to get around this would be to wrap an existing function to invoke your extra code, and call the method from the constructor:
export const TesteDecorator = (actionName): any => {
return (target?: any, propertyKey?: string): void => {
var prevFn: ()=> void = target['testeDecoratorHelper'];
target['testeDecoratorHelper'] = function() {
prevFn.call(this);
setTimeout(() => {
console.log(propertyKey);
this[propertyKey](); // when i execute here not have translateService on (this)
}, 10);
}
};
};
export class TesteComponent {
constructor(private translateService: TranslateService) {
this.testeDecoratorHelper();
setTimeout(() => {
this.teste(); // when i execute here translateService is on the (this)
}, 10);
}
testeDecoratorHelper() {
}
#TesteDecorator('ACTION')
teste() {
console.log(this);
}
#TesteDecorator('ACTION')
teste2() {
console.log(this);
}
}
The implementation above does not work with derived types, but it shoud get you started.
Edit
Since you are using Angular, instead of testeDecoratorHelper you could also use ngOnInit which has the advantage of being invoked automatically so you don't have to invoke it from the constructor. (10x to estus for this suggestions)

Categories

Resources