-
Notifications
You must be signed in to change notification settings - Fork 1
Quick start guide
interface FoodPlant {
process(ingredient: 'potato' | 'tomato'): number;
}
import { mock, instance, when } from 'omnimock';
// Step 1: Create a mock
const plantMock = mock<FoodPlant>('plantMock');
// Step 2: Define behaviors
when(plantMock.process('potato')).return(2);
// Step 3: Use your mock
const foodPlant = instance(plantMock);
foodPlant.process('potato') === 2; // true
foodPlant.process('tomato'); // Error: No behavior defined for <plantMock>.process('tomato')
This method works best for classes which belong to the C in MVC (services, controllers, ...). Check out mockInstance for mocking DTOs and other data objects.
Long call chains can be matched just as easily:
interface ProcessResult {
getQuantityOf(ingredient: string): number;
}
interface FoodPlant {
process(...ingredients: Array<string>): ProcessResult;
}
// ...
when(plantMock.process('potato', 'tomato').getQuantityOf('tomato')).return(4);
foodPlant.process('potato', 'tomato').getQuantityOf('tomato') === 4; // true
Use argument matchers to capture a large subset of possible calls at once:
import { anyOf } from 'omnimock';
when(plantMock.process(anyOf('potato', 'tomato'))).return(4);
foodPlant.process('tomato') === foodPlant.process('potato'); // true ( === 4)
See the full list of available matchers.
Using a custom function is a powerful way to customize the behavior of a mock.
import { anyString } from 'omnimock';
when(plantMock.process(anyString())).call(i => i.charAt(0) === 'p' ? 4 : 0);
foodPlant.process('potato') // 4
foodPlant.process('tomato') // 0
The documentation shows all possible ways to define the result of a call.
mockInstance(...)
is a shorthand for instance(mock(...))
.
interface Vegetable {
type: string;
edible: boolean;
}
import { mockInstance } from 'omnimock';
const edibleMock = mockInstance<Vegetable>('edibleMock', { edible: true });
expect(edibleMock.edible).toBe(true);
This technique is convenient for working with DTOs and other simple data objects.
Nested objects can be mocked with nested calls.
interface Vitamin {
letter: string;
}
interface Vegetable {
// ...
vitamins: Array<Vitamin>;
}
const ascorbic = mockInstance<Vegetable>('ascorbic', {
edible: true,
vitamins: [
mockInstance<Vitamin>('vitamin', { letter: 'c' })
]
});
If what you are mocking is an actual class, then you may pass that class as the first argument to mock
or mockInstance
.
Note: This does not work with abstract classes or interfaces.
// The regular usage: Specify the type and the name of the mock
const edibleMock = mockInstance<Vegetable>('edibleMock', { edible: true });
// Shorthand: the type and name of the mock are inferred.
const edibleMock = mockInstance(Vegetable, { edible: true });
// Works with `mock` too
const edibleMock = mock(Vegetable);
The nested example from the previous section can be rewritten like this:
const ascorbic = mockInstance(Vegetable, {
edible: true,
vitamins: [
mockInstance(Vitamin, { letter: 'c' })
]
});
In order for instanceof
to work properly, you need to use the constructor shorthand syntax:
const edibleMock = mock<Vegetable>('edibleMock', { edible: true });
instance(edibleMock) instanceof Vegetable // false
const edibleMock = mock(Vegetable, { edible: true });
instance(edibleMock) instanceof Vegetable // true
If you find yourself having to do a type cast, then most likely you are doing something wrong. Omnimock is designed to eliminate the need for casts.
const myVegetable: Vegetable = {
edible: true
} as Partial<Vegetable> as Vegetable; // A bad type cast!
// Instead, do:
const myVegetable = mockInstance<Vegetable>('myVegetable', {
edible: true
});
Why is the later better than the former? Omnimock's cast from Partial<T>
to T
is type-safe because it guarantees that no undefined behavior may occur. See Why use a mocking library for more details.
Type arguments of functions are lost when the function is mocked. This is due to a limitation in TypeScript's generics.
function myGeneric<T extends string>(t: T): T { return t; }
const dict = { hello: 'world' };
const mockGeneric = mock<typeof myGeneric>('myGeneric');
// Error: s should be of type 'hello' but it is instead of type 'string'
when(mockGeneric('hello')).call(s => dict[s]);
// You need an explicit cast to fix this
when(mockGeneric('hello')).call(s => dict[s as 'hello']);
Keep this in mind when dealing with generic methods or functions.