Typescript Types Mock… for real
Mocking interfaces/classes in your unit test could be annoying.
You often need to create your mock version and end up with a lot of mock object data in your test.
😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭
Problem
We have a simple person interface that is being used by a function that returns some of the information…
interface Person {
id: string;
getName(): string;
getAge(): number;
getChildren(): Person[];
}function PersonInformation(person: Person): string {
return `id=${person.id} - name=${ person.getName()}';
}
…in your test you need to provide a person object with methods that satisfy the interface completely unless you use casting (you are evil 😈 ).
Even with casting you need to provide the implementation of methods and properties that you need for the test.
const mockPerson: Person = {
id: '',
getName: () => '',
getAge: () => 0,
getChildren: () => [],
};const personInformation: string = PersonInformation(mockPerson);personInformation// 'id="" - name=""'
Typescript doesn’t keep interfaces after compilation so it’s not possible to create basic mocks from interfaces without giving them an implementation
What if there was a way to generate mocks from interface to make test development faster and nicer?
If I was creating a mock of Person from my imaginary mock creator I would like to automatically have the minimum default implementation like this example
const mock: Person = myImaginaryMockCreator<Person>();
mock.id // ""
mock.getName() // ""
mock.getAge // 0
mock.getChildren() // []
🤑
If I had this functionality at least my test would not fail for missing implementation
Is this a dream?? No
Solution
How do we improve our test? How do we automate our mocks so we can concentrate on the test instead of the mock objects?
There is a solution, and its called ts-auto-mock 💪
Ts-auto-mock
The intention of Ts-auto-mock is to create mocks from types. It doesn’t want to replace your test framework.
It’s a library that takes advantages of typescript transformer and it creates mocks for you. Let’s have a look of a basic usage and then we will talk about installation
import { createMock } from 'ts-auto-mock';const mock: Person = createMock<Person>();
Done. You call createMock with any type and it will create a basic mock with default values.
Installation
- install ts-auto-mock
Simple as npm install ts-auto-mock -D -E
2. Add the transformer ts-auto-mock/transformer to your compilation
This part is a bit tricky but there are different solutions based on what you are using. link
For the purpose of this article I’m not going to explain how to configure it in depth, hence I will assume that the transformer is already part of the compilation
Usage
import { createMock } from 'ts-auto-mock';const mock: Person = createMock<Person>();
createMock will create automatically:
- Object properties
- Methods properties
- Nested objects
If you have a recursive interface ts-auto-mock will make sure that the nested object is always present
interface Employee {
position: string;
boss: Employee
};const mock: Employee = createMock<Employee>();mock.boss.boss.boss.boss........... // It will not fail. It exists
This is the basic feature of ts-auto-mock. The library goes a bit further.
Extensions
Mostly of the time you will have your framework (jasmine, jest, aNewBeautifulTestFramework, etc ).
There are already two libraries that extends ts-auto-mock:
You can use them and follow their documentation.
If you want to understand how the extension works keep reading…
ts-auto-mock expose a factory where we can decide what it will happened every time it finds a method
Configure your mock
If we want to have a jasmine.Spy instead of the default method, we can configure ts-auto-mock to do it (this is what jasmine-ts-auto-mock and jest-ts-auto-mock do)
Note: This file needs to run before your tests.
import { MockFactory } from 'ts-auto-mock';MockFactory.instance.registerFactory((name: string, value: any) => { return jasmine.createSpy(name).and.returnValue(value);});
Provide your return type
declare module 'ts-auto-mock' {
interface MockMethod<TR> extends jasmine.Spy
}
Get the mock safely
The spy method exists inside the mock object but ts-auto-mock wants to be type safe and makes sure that you have a clear division between the instance of the mock (Person) and the customised mocked version.
Ts-auto-mock provide a small utility to get your functionality.
— — — — — — — — With a string- — — — — — — —
import { On, method } from 'ts-auto-mock';let nameSpy = On(mockPerson).get(method('getName'));
— — — — — — — -With method selection — — — — — — —
import { On, method } from 'ts-auto-mock';nameSpy = On(mockPerson).get(method(mock => mock.getName));
Done.
Now if we test again our methods we can get the spies
import { createMock, On, method } from 'ts-auto-mock';interface Person {
getName(): string;
getSurname(): string;
}
function personFullName(person: Person): string {
return person.getName() + person.getSurname();
}const mock: Person = createMock<Person>();
const nameSpy: jasmine.Spy = On(mock).get(method('getName'));
const surnameSpy: jasmine.Spy = On(mock).get(method('getSurname'));personFullName(person);// ""
expect(nameSpy).toHaveBeenCalled();
expect(surnameSpy).toHaveBeenCalled();
Conclusion
Ts-auto-mock has few benefits:
- Real mocks types
- Less code in your unit test
- Recursive mocks
- Can be extended. Is not coupled to a specific testing framework
The main disadvantages is the configuration that hopefully will be solved once Typescript will add support for custom plugin directly in the tsconfig.
About the library
These libraries have been created by me and Giulio Caprino. (It wouldn’t be possible without him).
We are planning to add new feature to this library to improve performance and more automatic custom mocks
Also I would like to thanks the creator of ts-transformers-keys. I’ve used his library for the first couple of days to understand a bit more on how to create a typescript transformer
Another special thanks to the creator of ttypescript. Thanks to his library jest-ts-auto-mock can be used with a rather easy configuration along jest