I have the following class which has a chunk defined as an array and multiple objects get pushed into this array this.chunk.
SearchController.ts
#Injectable()
export class SearchController {
private chunk: any[] = [];
readonly CHUNK_SIZE: number = 100;
public async post(names) {
this.chunk.push(names);
if (this.chunk.length >= this.CHUNK_SIZE) {
return true;
}
return false;
}
}
I want to be able to either mock the CHUNK_SIZE to a number equal to 1 or maybe be able to change the value of the chunk.length
Below is my test
SearchController.test.ts
it('should return true for chunk_size 1', async () => {
const actual = await queue.post({action: 'UPDATE'});
expect(actual).toBeTruthy();
});
I have tried using jest.spyOn() but it didn't work.
What am I missing?
Would really appreciate if anyone can help. thanks.
You can use the following trick inside of it block:
Object.defineProperty(<instanceOfController>, 'CHUNK_SIZE', { value: <anyRandomValue>, writable: false });
For example:
it('should return true for chunk_size 1', async () => {
Object.defineProperty(queue, 'CHUNK_SIZE', { value: 1, writable: false });
const actual = await queue.post({ action: 'UPDATE' });
expect(actual).toBeTruthy();
});
My technique for such cases is to create a derived test-specific class:
class SearchControllerTest extends SearchController {
override readonly CHUNK_SIZE = 1; // property cannot be private in parent class
}
… and then mock the SearchController.ts module with the new class. If necessary, I use jest.requireActual() to get the true value of the module.
Related
I want to write a constructor function that takes some predefined methods from an object (Methods) and injects it into every new object that is created with the constructor. It injects methods from another object because, I want the consumers of my module to be able to add new methods.
But the problem is: as all the injected methods are not defined in the constructor I can't seem to manage their type annotations properly.
It's hard for me to describe the problem so I created a simple example (with JavaScript to avoid all the type error) to demonstrate it.
// methods.js ---------------------------------------
const methods = {};
const addMethod = (name, value) => (methods[name] = value);
// these methods should be added by an external user of the programmer.js module
function code(...task) {
this.addInstruction({
cmd: "code",
args: task
});
return this;
}
function cry(...words) {
this.addInstruction({
cmd: "cry",
args: words
});
return this;
}
addMethod("code", code);
addMethod("cry", cry);
// programmer.js -------------------------------------
const retriveInstructionsMethod = "SECRET_METHOD_NAME";
const secretKey = "VERY_SECRET_KEY";
function Programmer() {
const instructions = [];
this.addInstruction = (value) => instructions.push(value);
Object.defineProperty(this, retriveInstructionsMethod, {
enumerable: false,
writable: false,
value(key) {
if (key === secretKey) return instructions;
},
});
for (const key of Object.keys(methods))
this[key] = (...args) => methods[key].apply(this, args);
}
// test.js -------------------------------------------
const desperateProgrammer = new Programmer();
const instructions = desperateProgrammer
.code("A library in typescript within 10 days")
.cry("Oh God! Why everything is so complicated :'( ? Plz Help!!!")
// the next two lines shouldn't work here (test.js) as the user
// shouln't have asscess to the "retriveInstructionsMethod" and "secretKey"
// keys. I'm just showing it to demonstrate what I want to achieve.
[retriveInstructionsMethod](secretKey);
console.log(instructions);
Here I want to hide all the instructions given to a Programmer object. If I hide it from the end user then I won't have to validate those instructions before executing them later.
And the user of programmer.js module should be able to add new methods to a programmer. For example:
// addMethods from "methods.js" module
addMethods("debug", (...bugs) => {...});
Now I know that, I can create a base class and just extend it every time I add a new method. But as it is expected that there will be lots of external methods so soon it will become very tedious for the user.
Below is what I've tried so far. But the type annotation clearly doesn't work with the following setup and I know it should not! Because the Methods interface's index signature([key: string]: Function) is very generic and I don't know all the method's name and signature that will be added later.
methods.ts
export interface Methods {
[key: string]: Function;
}
const methods: Methods = {};
export default function getMethods(): Methods {
return { ...methods };
}
export function addMethods<T extends Function>(methodName: string, method: T) {
methods[methodName] = method;
}
programmer.ts
import type { Methods } from "./methods";
import getMethods from "./methods";
export type I_Programmer = Methods & {
addInstruction: (arg: { cmd: string; args: unknown[] }) => void;
};
interface ProgrammerConstructor {
new (): I_Programmer;
(): void;
}
const retriveInstructionsMethod = "SECRET_METHOD_NAME";
const secretKey = "ACCESS_KEY";
const Programmer = function (this: I_Programmer) {
const instructions: object[] = [];
this.addInstruction = (value) => instructions.push(value);
Object.defineProperty(this, retriveInstructionsMethod, {
enumerable: false,
writable: false,
value(key: string) {
if (key === secretKey) return instructions;
},
});
const methods = getMethods();
for (const key of Object.keys(methods))
this[key] = (...args: unknown[]) => methods[key].apply(this, args);
} as ProgrammerConstructor;
// this function is just to demonstrate how I want to extract all the
// instructions. It should not be accessible to the end user.
export function getInstructionsFrom(programmer: I_Programmer): object[] {
// gives error
// #ts-expect-error
return programmer[retriveInstructionsMethod][secretKey]();
}
export default Programmer;
testUsages.ts
import { addMethods } from "./methods";
import type { I_Programmer } from "./programmer";
import Programmer, { getInstructionsFrom } from "./programmer";
function code(this: I_Programmer, task: string, deadline: string) {
this.addInstruction({ cmd: "code", args: [task, deadline] });
return this;
}
function cry(this: I_Programmer, words: string) {
this.addInstruction({ cmd: "cry", args: [words] });
return this;
}
addMethods("code", code);
addMethods("cry", cry);
const desperateProgrammer = new Programmer()
.code("a library with typescript", "10 days") // no type annotation of "code" method
.cry("Oh God! Why everything is so complicated :'( ? Plz Help!!!"); // same here
// Just for demonstration. Should not be accessible to the end user!!!
console.log(getInstructionsFrom(desperateProgrammer));
Kindly give me some hint how I can solve this problem. Thanks in advance.
Let's say I have the following files/code:
Person.ts
export class Person {
id: string;
firstName: string;
lastName: string;
isEmployed: boolean = true;
isManager: boolean = false;
public static Name = ():string => this.firstName + ' ' + this.lastName;
}
WorkHistory.ts
import { Person } from './person';
export class WorkHistory {
public propA: string;
public propB: string;
public getHistory = (p: Person): any => {
// do something
// return history
};
}
Formatter.ts
import { Person } from './person';
import { WorkHistory } from './workHistory';
export class Formatter {
public formatWork(p: Person) {
let wh: WorkHistory = new WorkHistory();
let whData = wh.getHistory(p);
// do formatting
// return formatting
}
}
I'm trying to write a unit test for the formatWork method. However, I can't figure out how to stub out WorkHistory and its properties.
Here's what I've got so far:
Formatter.spec.ts
describe('formatWork', () => {
let mockWorkHistory = {
propA: '',
propB: ''
};
let sandbox;
let formatter;
beforeEach(() => {
sandbox = sinon.createSandbox();
sandbox.stub(WorkHistory, "prototype").value(mockWorkHistory);
});
afterEach(() => {
sandbox.restore();
});
it('should do something', () => {
// create person object
formatter = new Formatter();
var result = formatter.formatWork(person);
console.log(result);
});
});
I've tried stubs and sandbox.replace, however, I can't seem to overwrite WorkHistory default properties or methods.
As it stands, the above throws an exception in Phantom 2.1.1:
TypeError: Attempting to change enumerable attribute of unconfigurable property.
Additionally, the console.log in my fixture shows all of the default properties for WorkHistory instead of the overwritten values.
What am I missing? What am I doing wrong?
I ended up having to use ts-mock-imports since I was using webpack. Thanks!
The Sinon only can't cover all your requirements. You need some tools to mock your import, like genMockFromModule in Jest or proxyquire
Sinon is a pretty convenient way to generate stub, but the tool knows nothing about your module structure. And you should find a way how to pass your Sinon-mocked class to the module that you are testing.
I have these two methods which are almost similar:
private firstFunction () {
this.serviceOne.methodOne().subscribe(
res => {
return resultOne = res;
},
err => {}
);
}
private secondFunction () {
this.serviceTwo.methodTwo().subscribe(
res => {
return resultTwo = res;
},
err => {}
);
}
I want to write a generic function, like this:
genericFunction (service ,method , result ) {
service.method().subscribe(
res => {
return result = res;
},
err => {}
);
}
And consequently I want to get something like this working:
genericFunction (serviceOne , methodOne , resultOne );
genericFunction (serviceTwo , methodTwo , resultTwo );
Actually, I cannot find how to pass methodOne and methodTwo as params. Any sugestions?
There are several issues in your code.
Firstly, you want to modify the field you pass in as a parameter (as suggested by result = res. You can't pass in a reference to a field, but you can pass in the field name, and use indexing to change the field. keyof T will allow you to pass in the field in a type safe way.
Secondly if you want to access a method on a service. Again we can do this passing in the method name, and we can constrain the service to have a method with the passed in method name, that returns an Observable. The result of the Observable can also be constrained to be of the same type of the field we are going to assign it to in order for the method to be fully type safe.
declare class Service1 {
method1() : Observable<number>
}
declare class Service2 {
method2() : Observable<string>
}
class MyClass {
resultOne!: number;
resultTwo!: string;
constructor() {
this.genericFunction(new Service1(), "method1", "resultOne");
this.genericFunction(new Service2(), "method2", "resultTwo");
this.genericFunction(new Service1(), "method1", "resultTwo"); // error resultTwo is a string, the method return Observable<number>
this.genericFunction(new Service2(), "method", "resultTwo"); // error method does not exit on Service2
this.genericFunction(new Service2(), "method2", "resultTwo2"); // error field does not exist on type
}
genericFunction<MethodKey extends string, ResultKey extends keyof MyClass>(service:Record<MethodKey, ()=> Observable<MyClass[ResultKey]>>, method:MethodKey, result: ResultKey){
service[method]().subscribe(
res => this[result] = res,
err => {}
);
}
}
Note We could have also passed in the function as a function not just as a name, but directly a typed function. The disadvantage of this is that we either have to use bind to ensure the service method will still have the correct this when it's called, or use an arrow function when calling (again to ensure the service method has the correct this). This is error prone though, bind results in an untyped function, so we can't check compatibility to the field, and someone might pass service.method directly and no error would be reported until runtime:
class MyClass {
resultOne!: number;
resultTwo!: string;
constructor() {
var service1 = new Service1()
var service2 = new Service2()
this.genericFunction(()=> service1.method1(), "resultOne");
this.genericFunction(()=> service2.method2(), "resultTwo");
this.genericFunction(service2.method2, "resultTwo"); // no error, depending on the implementation of method2 it might or might not work
this.genericFunction(service2.method2.bind(service2), "resultOne"); // no error, the service call will work, but we store it in an incompatible variable
this.genericFunction(()=> service1.method1(), "resultTwo");// error resultTwo is a string, the method return Observable<number>
this.genericFunction(()=> service2.method2(), "resultTwo2");// // error field does not exist on type
}
genericFunction<MethodKey extends string, ResultKey extends keyof MyClass>(method:()=> Observable<MyClass[ResultKey]>, result: ResultKey){
method().subscribe(
res => this[result] = res,
err => {}
);
}
}
try by using the following code:
private firstFunction () {
let response= genericFunction(this.serviceOne.methodOne())
}
private secondFunction () {
let response = genericFunction(this.serviceTwo.methodTwo())
}
Modify you Generic Function by just receiving a variable.
//if it is angular 4 or less
genericFunction (method: Observable) {
return method.map(res => {
return res.json();
});
}
//if it is angular 5 or 6
genericFunction (method: Observable) {
return method.pipe(
map(res => {
return res;
}));
}
I'd like to augment, but not completely replace, instances of a mocked constructor in a Jest unit test.
I want to add a few values to the instance, but keep the auto-mocked goodness of Jest.
For example:
A.js
module.exports = class A {
constructor(value) {
this.value = value;
}
getValue() {
return this.value;
}
}
To get some auto-mock awesomeness:
jest.mock('./A');
With the automock, instances have a mocked .getValue() method, but they do not have the .value property.
A documented way of mocking constructors is:
// SomeClass.js
module.exports = class SomeClass {
m(a, b) {}
}
// OtherModule.test.js
jest.mock('./SomeClass'); // this happens automatically with automocking
const SomeClass = require('./SomeClass')
const mMock = jest.fn()
SomeClass.mockImplementation(() => {
return {
m: mMock
}
})
const some = new SomeClass()
some.m('a', 'b')
console.log('Calls to m: ', mMock.mock.calls)
Using that approach for A:
jest.mock('./A');
const A = require('./A');
A.mockImplementation((value) => {
return { value };
});
it('does stuff', () => {
const a = new A();
console.log(a); // -> A { value: 'value; }
});
The nice thing about that is you can do whatever you want to the returned value, like initialize .value.
The downsides are:
You don't get any automocking for free, e.g. I'd need to add .getValue() myself to the instance
You need to have a different jest.fn() mock function for each instance created, e.g. if I create two instances of A, each instance needs its own jest.fn() mock functions for the .getValue() method
SomeClass.mock.instances is not populated with the returned value (GitHub ticket)
One thing that didn't work (I was hoping that maybe Jest did some magic):
A.mockImplementation((value) => {
const rv = Object.create(A.prototype); // <- these are mocked methods
rv.value = value;
return rv;
});
Unfortunately, all instances share the same methods (as one would expect, but it was worth a shot).
My next step is to generate the mock, myself, via inspecting the prototype (I guess), but I wanted to see if there is an established approach.
Thanks in advance.
Turns out this is fixed (as of jest 24.1.0) and the code in the question works, as expected.
To recap, given class A:
A.js
module.exports = class A {
constructor(value) {
this.value = value;
}
setValue(value) {
this.value = value;
}
}
This test will now pass:
A.test.js
jest.mock('./A');
const A = require('./A');
A.mockImplementation((value) => {
const rv = Object.create(A.prototype); // <- these are mocked methods
rv.value = value;
return rv;
});
it('does stuff', () => {
const a = new A('some-value');
expect(A.mock.instances.length).toBe(1);
expect(a instanceof A).toBe(true);
expect(a).toEqual({ value: 'some-value' });
a.setValue('another-value');
expect(a.setValue.mock.calls.length).toBe(1);
expect(a.setValue.mock.calls[0]).toEqual(['another-value']);
});
The following worked for me:
A.mockImplementation(value => {
const rv = {value: value};
Object.setPrototypeOf(rv, A.prototype);
return rv
})
I have an application built on typescript with decorators for some convenience property assignments and wondering how I can go about writing unit tests for them.
export function APIUrl() {
return function (target: any, key: string) {
let _value = target[key];
function getter() {
return _value;
}
function setter(newValue) {
_value = getApiURL();
}
if (delete target[key]) {
Object.defineProperty(target, key, {
get: getter,
set: setter
});
}
};
}
In a spec class I have,
it("should return url string", ()=> {
#APIUrl();
let baseURL:string;
expect(baseURL typeOf string).toBe(true)
})
Since decorators are just functions I would suggest to just test them like any other function. And only if you really need to, add one tests that shows how to use the decorator with a class/member/...
Here is an example such a test could look like:
import test from 'ava';
import { APIUrl } from './path';
const decorate = new APIUrl();
test.before(t => {
let obj = { someProp: 'foo' };
decorate(obj, 'someProp');
t.context.foo = obj;
});
test('should return original value', t => {
t.is(t.context.foo.someProp, 'foo');
});
Another approach could be to setup some properties and/or methods that use your decorators and test their usage directly.
Note: decorators can only be used on class methods and members so you'd need to create a dummy class in your test.
Here's an example:
//Test Setup
class Test {
#APIUrl()
url: string;
#AnotherDecorator()
anotherFunction() {}
}
//Unit tests
describe('Decorator Tests', () => {
it('should work', () => {
const t = new Test();
expect(t.url).toEqual("something");
expect(t.anotherFunction()).toReturn("something else");
});
}