@otbe asked me to implement some decorators to help during unit testing. This is really interesting and I believe that there are a lot of things that we can use to improve the developers testing experience.
My plan is to develop an stand alone npm package. It will contain some testing utils.
The initial request refers to decorators like @mock and @mockSingleton:
export function mock<Interface> (binding: string|Symbol|INewable<Interface>) {
return (targetClass: INewable<Interface>): void => {
injectable()(targetClass);
kernel.unbind(binding);
kernel.bind(binding).to(targetClass);
}
}
@mock(MyService)
class MyServiceMock {
name:string = 'foo';
}
function mockSingleton<Interface> (binding: string|Symbol|INewable<Interface>) {
return (targetClass: INewable<Interface>): void => {
kernel.unbind(binding);
kernel.bind(binding).toConstantValue(new targetClass);
}
}
I'm creating this issue to keep track of all the requests and ideas.
If you want to suggest something please add a comment or join us on Gitter. I will collect the feedback from the chat and add it here...
After some time using @mock Im not that happy with it :/ I want to share my mocks across several tests and if I do one time:
@mock(MyService)
export class MyServiceMock {
foo(): number {
return;
}
}
MyService is mocked for every test, because my kernel is shared too. That may be fine for services that use MyService, but if I test MyService itself I have to unbind the mock. So at the end I have to ensure that I get the correct version (mocked or not mocked) of MyService in every test. But a bigger problem is, that I have to kernel.get(MyService) in beforeEach for getting an instance of this mock, because I want to spy on it and modify the behavior of methods.
Im switched to something like that:
export function setupSingletonMock<T>(target: string | Symbol | INewable<any>, mock: { new(...args: any[]): T; }): T {
kernel.unbind(target);
kernel.bind<T>(target).to(mock).inSingletonScope();
return kernel.get<T>(target);
}
and use it like
let myServiceMock;
beforeEach(() => {
myServiceMock = setupSingletonMock(MyService, MyServiceMock);
});
it('test', () =>聽{
spyOn(myServiceMock, 'foo').andReturn(5);
});
What do you think about it?
Just adding here some ideas mentioned on Gitter...
import { TestableKernel } from "inversify-testing-utils";
import { kernel } from "../src/kernel";
// TestableKernel is a superset of Kernel (includes snapshot/restoreSnapshot)
let testableKernel = new TestableKernel(kernel);
// snapshot of original kernel
testableKernel.snapshot();
testableKernel.unbind(MyService);
testableKernel.bind(MyService).to(MyServiceMock);
// do something...
// return to snapshot
testableKernel.restoreSnapshot();
keep in mind that restore must modify the original kernel
import { kernel } from './shared/kernel'; // this kernel includes all bindings from my app ->
kernel.snapshot() // create a snapshot of the current bindings -> lets say "MyService bound to MyService"
kernel.unbind(MyService);
kernel.bind(MyService).to(MyServiceMock); // "MyService bound to MyServiceMock"
// do something
kernel.restoreSnapshot(); // "MyService bound to MyService"
Snapshot is available thanks to #207 by @otbe I'm going to leave this open for now. I want to do some thinking about things like auto-mocking...
I think this can be closed for now? I will probably remove the project as well until we have a better idea.... I will think about this once 2.0.0 becomes gold.
Most helpful comment
Just adding here some ideas mentioned on Gitter...