Fork of ts-mockito, which will be kept until the following PRs are accepted, or similar functionality is added to ts-mockito:
- Adding support for mocking interfaces
- Adding support for verify(...).timeout(ms)
- Matcher types
- Mock free functions
- Add defer() for testing deferred promise resolution
- Add warning when forgetting to call instance()
- Support enumerating properties on mocks
- Adding
nextTick()
- Better types on
anyFunction()
andthenCall()
- Better call verification error messages
- Mocking of constructors
- Make
.thenResolve()
work forPromiseLike<T>
- Make spying on an object prototype work
- Type safe
objectContaining()
- Date matchers
- Numberic matchers
npm install @johanblumenberg/ts-mockito --save-dev
This feature is useful when testing asynchronous functionality. You do some action and expect the result to arrive as an asynchronous function call to one of your mocks.
let mockedFoo:Foo = mock(Foo);
await verify(mockedFoo.getBar(3)).timeout(1000);
Mocking interfaxces works just the same as mocking classes, except you
must use the imock()
function to create the mock.
let mockedFoo:Foo = imock(); // Foo is a typescript interface
when(mockedFoo.getBar(5)).thenReturn('five');
It also works for properties.
let mockedFoo:Foo = imock(MockPropertyPolicy.StubAsProperty);
when(mockedFoo.bar).thenReturn('five');
For interface mocks, you can set the defauklt behviour for mocked properties that have no expectations set. They can behave eiter as a property, returning null, or as a function, returning a function that returns null, or throw an exception.
let mockedFoo1:Foo = imock(MockPropertyPolicy.Throw);
instance(mockedFoo1).bar; // This throws an exception, because there is no expectation set on the bar property
let mockedFoo2:Foo = imock(MockPropertyPolicy.Throw);
when(mockedFoo2.bar).thenReturn('five');
instance(mockedFoo2).bar; // Now this returns 'five', and no exception is thrown, because there is an expectation set on the bar property
let mockedFoo3:Foo = imock(MockPropertyPolicy.StubAsProperty);
instance(mockedFoo3).bar; // This returns null, because no expectation is set
let mockedFoo4:Foo = imock(MockPropertyPolicy.StubAsMethod);
instance(mockedFoo4).getBar(5); // This returns null, because no expectation is set
Sometimes you need to mock a function, not an object, for example to pass as a callback somewhere. This can be done using fnmock()
. It works just like any other mock, except it's a function, not an object.
let fn: (a: number, b: string) => number = fnmock();
when(fn(10, 'hello')).thenReturn(5);
instance(fn)(10, 'hello'); // returns 5
verify(fn(10, 'hello')).called();
Sometimes you need to mock a constructor, and control creation of new objects.
let mockedFooCtor: new () => Foo = cmock();
let mockedFoo:Foo = imock();
when(new mockedFooCtor()).thenReturn(instance(mockedFoo));
const result = new (instance(mockedFooCtor))();
verify(new mockedFooCtor()).called();
expect(result).toBe(instance(mockedFoo));
The actions .thenResolve()
and .thenReject()
are returning promises that are already resolved or rejected. Sometimes you want to control the order or timing of when promises are resolved. In that case it is useful to return a deferred promise, and resolve it from the test code, when appropriate.
let d = defer<number>();
when(obj.method()).thenReturn(d); // Return a promise that is not resolved yet
d.resolve(1); // Later, the promise is resolved or rejected
It's possible to mock props for react components when testing using enzyme.
let props: Props = imock(MockPropertyPolicy.StubAsProperty);
when(props.text).thenReturn('OK');
when(props.onClick()).thenReturn();
let c = mount(<MyButton {...instance(props)}>);
// Creating mock
let mockedFoo:Foo = mock(Foo);
// Getting instance from mock
let foo:Foo = instance(mockedFoo);
// Using instance in source code
foo.getBar({name: "John Doe"});
foo.getBar({name: "John Smith"});
// Explicit, readable verification
verify(mockedFoo.getBar(objectContaining({name: startsWith("John")}))).twice();
// Creating mock
let mockedFoo:Foo = mock(Foo);
// Getting instance from mock
let foo:Foo = instance(mockedFoo);
// Using instance in source code
foo.getBar(3);
// Using anything() is identical to using _
verify(mockedFoo.getBar(anything())).called();
verify(mockedFoo.getBar(_)).called();
Matching by JSON works very similar to objectContaining()
, to be used when the value to match is a string containing a JSON formatted object instead of just an object. This allows to partially match the JSON content.
// Creating mock
let mockedFoo:Foo = mock(Foo);
// Getting instance from mock
let foo:Foo = instance(mockedFoo);
// Using instance in source code
foo.getBar('{"name": "John Doe", "age": 42}');
foo.getBar('{"name": "John Smith", "age": 30}');
// Match by part of JSON string
verify(mockedFoo.getBar(jsonContaining({name: startsWith("John")}))).twice();
verify(mockedFoo.getBar(jsonContaining({age: 42}))).once();
Sometimes it is useful to define custom matchers, to be able to easily match on anything. This can be done by extending the Matcher
class.
class IsPalindromeMatcher extends Matcher {
public match(value: string): boolean {
return value === value.split("").reverse().join("");
}
public toString(): string {
return 'isPalindrome()';
}
}
function isPalindrome(): string {
return new IsPalindromeMatcher() as any;
}
// Creating mock
let mockedFoo:Foo = mock(Foo);
// Getting instance from mock
let foo:Foo = instance(mockedFoo);
// Using instance in source code
foo.bar("racecar");
// Match using the custom matcher
verify(mockedFoo.bar(isPalindrome())).once();
// Creating mock
let mockedFoo:Foo = mock(Foo);
// Getting instance from mock
let foo:Foo = instance(mockedFoo);
// Using instance in source code
foo.getBar(3);
foo.getBar(5);
// Explicit, readable verification
verify(mockedFoo.getBar(3)).called();
verify(mockedFoo.getBar(5)).called();
// Creating mock
let mockedFoo:Foo = mock(Foo);
// stub method before execution
when(mockedFoo.getBar(3)).thenReturn('three');
// Getting instance
let foo:Foo = instance(mockedFoo);
// prints three
console.log(foo.getBar(3));
// prints null, because "getBar(999)" was not stubbed
console.log(foo.getBar(999));
// Creating mock
let mockedFoo:Foo = mock(Foo);
// stub getter before execution
when(mockedFoo.sampleGetter).thenReturn('three');
// Getting instance
let foo:Foo = instance(mockedFoo);
// prints three
console.log(foo.sampleGetter);
Syntax is the same as with getter values.
Please note, that stubbing properties that don't have getters only works if Proxy object is available (ES6).
// Creating mock
let mockedFoo:Foo = mock(Foo);
// Getting instance
let foo:Foo = instance(mockedFoo);
// Some calls
foo.getBar(1);
foo.getBar(2);
foo.getBar(2);
foo.getBar(3);
// Call count verification
verify(mockedFoo.getBar(1)).once(); // was called with arg === 1 only once
verify(mockedFoo.getBar(2)).twice(); // was called with arg === 2 exactly two times
verify(mockedFoo.getBar(between(2, 3))).thrice(); // was called with arg beween 2-3 exactly three times
verify(mockedFoo.getBar(anyNumber()).times(4); // was called with any number arg exactly four times
verify(mockedFoo.getBar(2)).atLeast(2); // was called with arg === 2 min two times
verify(mockedFoo.getBar(1)).atMost(1); // was called with arg === 1 max one time
verify(mockedFoo.getBar(4)).never(); // was never called with arg === 4
// Creating mock
let mockedFoo:Foo = mock(Foo);
let mockedBar:Bar = mock(Bar);
// Getting instance
let foo:Foo = instance(mockedFoo);
let bar:Bar = instance(mockedBar);
// Some calls
foo.getBar(1);
bar.getFoo(2);
// Call order verification
verify(mockedFoo.getBar(1)).calledBefore(mockedBar.getFoo(2)); // foo.getBar(1) has been called before bar.getFoo(2)
verify(mockedBar.getFoo(2)).calledAfter(mockedFoo.getBar(1)); // bar.getFoo(2) has been called before foo.getBar(1)
verify(mockedFoo.getBar(1)).calledBefore(mockedBar.getFoo(999999)); // throws error (mockedBar.getFoo(999999) has never been called)
let mockedFoo:Foo = mock(Foo);
when(mockedFoo.getBar(10)).thenThrow(new Error('fatal error'));
let foo:Foo = instance(mockedFoo);
try {
foo.getBar(10);
} catch (error:Error) {
console.log(error.message); // 'fatal error'
}
You can also stub method with your own implementation
let mockedFoo:Foo = mock(Foo);
let foo:Foo = instance(mockedFoo);
when(mockedFoo.sumTwoNumbers(anyNumber(), anyNumber())).thenCall((arg1:number, arg2:number) => {
return arg1 * arg2;
});
// prints '50' because we've changed sum method implementation to multiply!
console.log(foo.sumTwoNumbers(5, 10));
You can also stub method to resolve / reject promise
let mockedFoo:Foo = mock(Foo);
when(mockedFoo.fetchData("a")).thenResolve({id: "a", value: "Hello world"});
when(mockedFoo.fetchData("b")).thenReject(new Error("b does not exist"));
You can reset just mock call counter
// Creating mock
let mockedFoo:Foo = mock(Foo);
// Getting instance
let foo:Foo = instance(mockedFoo);
// Some calls
foo.getBar(1);
foo.getBar(1);
verify(mockedFoo.getBar(1)).twice(); // getBar with arg "1" has been called twice
// Reset mock
resetCalls(mockedFoo);
// Call count verification
verify(mockedFoo.getBar(1)).never(); // has never been called after reset
Or reset mock call counter with all stubs
// Creating mock
let mockedFoo:Foo = mock(Foo);
when(mockedFoo.getBar(1)).thenReturn("one").
// Getting instance
let foo:Foo = instance(mockedFoo);
// Some calls
console.log(foo.getBar(1)); // "one" - as defined in stub
console.log(foo.getBar(1)); // "one" - as defined in stub
verify(mockedFoo.getBar(1)).twice(); // getBar with arg "1" has been called twice
// Reset mock
resetStubs(mockedFoo);
console.log(foo.getBar(1)); // null - previously added stub has been removed
Resetting a mock will reset both call count and stub calls. For a spy it will also reset the original object, removing the spy.
// Creating mock
let mockedFoo:Foo = mock(Foo);
when(mockedFoo.getBar(1)).thenReturn("one").
// Getting instance
let foo:Foo = instance(mockedFoo);
// Some calls
console.log(foo.getBar(1)); // "one" - as defined in stub
console.log(foo.getBar(1)); // "one" - as defined in stub
verify(mockedFoo.getBar(1)).twice(); // getBar with arg "1" has been called twice
// Reset mock
resetStubs(mockedFoo);
// Call count verification
verify(mockedFoo.getBar(1)).never(); // has never been called after reset
console.log(foo.getBar(1)); // null - previously added stub has been removed
let mockedFoo:Foo = mock(Foo);
let foo:Foo = instance(mockedFoo);
// Call method
foo.sumTwoNumbers(1, 2);
// Check first arg captor values
const [firstArg, secondArg] = capture(mockedFoo.sumTwoNumbers).last();
console.log(firstArg); // prints 1
console.log(secondArg); // prints 2
You can also get other calls using first()
, second()
, byCallIndex(3)
and more...
It is also possible to capture arguments of a specific invocation, by providing
matchers in the same way as when using when()
or verify()
.
let mockedFoo:Foo = mock(Foo);
let foo:Foo = instance(mockedFoo);
// Call method
foo.sumTwoNumbers(1, 1);
foo.sumTwoNumbers(2, 9);
foo.sumTwoNumbers(3, 1);
// Check first arg captor values
const [firstArg, secondArg] = capture(mockedFoo.sumTwoNumbers, [2, _]).last();
console.log(firstArg); // prints 2
console.log(secondArg); // prints 9
You can set multiple returning values for same matching values
const mockedFoo:Foo = mock(Foo);
when(mockedFoo.getBar(anyNumber())).thenReturn('one').thenReturn('two').thenReturn('three');
const foo:Foo = instance(mockedFoo);
console.log(foo.getBar(1)); // one
console.log(foo.getBar(1)); // two
console.log(foo.getBar(1)); // three
console.log(foo.getBar(1)); // three - last defined behavior will be repeated infinitely
Another example with specific values
let mockedFoo:Foo = mock(Foo);
when(mockedFoo.getBar(1)).thenReturn('one').thenReturn('another one');
when(mockedFoo.getBar(2)).thenReturn('two');
let foo:Foo = instance(mockedFoo);
console.log(foo.getBar(1)); // one
console.log(foo.getBar(2)); // two
console.log(foo.getBar(1)); // another one
console.log(foo.getBar(1)); // another one - this is last defined behavior for arg '1' so it will be repeated
console.log(foo.getBar(2)); // two
console.log(foo.getBar(2)); // two - this is last defined behavior for arg '2' so it will be repeated
Short notation:
const mockedFoo:Foo = mock(Foo);
// You can specify return values as multiple thenReturn args
when(mockedFoo.getBar(anyNumber())).thenReturn('one', 'two', 'three');
const foo:Foo = instance(mockedFoo);
console.log(foo.getBar(1)); // one
console.log(foo.getBar(1)); // two
console.log(foo.getBar(1)); // three
console.log(foo.getBar(1)); // three - last defined behavior will be repeated infinity
Possible errors:
const mockedFoo:Foo = mock(Foo);
// When multiple matchers, matches same result:
when(mockedFoo.getBar(anyNumber())).thenReturn('one');
when(mockedFoo.getBar(3)).thenReturn('one');
const foo:Foo = instance(mockedFoo);
foo.getBar(3); // MultipleMatchersMatchSameStubError will be thrown, two matchers match same method call
You can mock abstract classes
const mockedFoo: SampleAbstractClass = mock(SampleAbstractClass);
const foo: SampleAbstractClass = instance(mockedFoo);
You can also mock generic classes, but note that generic type is just needed by mock type definition
const mockedFoo: SampleGeneric<SampleInterface> = mock(SampleGeneric);
const foo: SampleGeneric<SampleInterface> = instance(mockedFoo);
You can partially mock an existing instance:
const foo: Foo = new Foo();
const spiedFoo = spy(foo);
when(spiedFoo.getBar(3)).thenReturn('one');
console.log(foo.getBar(3)); // 'one'
console.log(foo.getBaz()); // call to a real method
You can spy on plain objects too:
const foo = { bar: () => 42 };
const spiedFoo = spy(foo);
foo.bar();
console.log(capture(spiedFoo.bar).last()); // [42]
Sometimes it is useful to be able to see all invocations on a mocked object, for example if you are testing a component which you are not sure exactly how it works.
let mockedFoo:Foo = imock({logInvocations: true});
let foo:Foo = instance(mockedFoo);
foo.bar("hello world");
// This will log:
// call: bar("hello world")
Sometimes it is useful to invoke the original method on a spy and change the return value. Mocking the method using when(object.method()).thenReturn(newValue)
can be done, but this will not invoke the original method, and it will not be possible to use the original return value and modify it before it is returned.
This can be accomplished using thenCall()
and invoke the original method.
const foo: Foo = new Foo();
let spiedFoo = spy(foo);
when(spiedFoo.method(_, _)).thenCall(function (arg1, arg2) {
const originalResult = this.proceed(arg1, arg2);
return "modified " + originalResult;
});
- Szczepan Faber (https://www.linkedin.com/in/szczepiq)
- Sebastian Konkol (https://www.linkedin.com/in/sebastiankonkol)
- Clickmeeting (http://clickmeeting.com)
- Michał Stocki (https://github.com/michalstocki)
- Łukasz Bendykowski (https://github.com/viman)
- Andrey Ermakov (https://github.com/dreef3)
- Markus Ende (https://github.com/Markus-Ende)
- Thomas Hilzendegen (https://github.com/thomashilzendegen)
- Johan Blumenberg (https://github.com/johanblumenberg)
- Lorens León (https://github.com/leon19)