@johanblumenberg/ts-mockito
Version:
Mocking library for TypeScript
688 lines (485 loc) • 20.4 kB
Markdown
# /ts-mockito
Fork of [ts-mockito](https://github.com/NagRock/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](https://github.com/NagRock/ts-mockito/pull/76)
- [Adding support for verify(...).timeout(ms)](https://github.com/NagRock/ts-mockito/pull/97)
- [Matcher types](https://github.com/NagRock/ts-mockito/pull/139)
- [Mock free functions](https://github.com/NagRock/ts-mockito/pull/140)
- [Add defer() for testing deferred promise resolution](https://github.com/NagRock/ts-mockito/pull/141)
- [Add warning when forgetting to call instance()](https://github.com/johanblumenberg/ts-mockito/commit/e2b52a77324136d8b6a8aabf51eec8babaca221b)
- [Support enumerating properties on mocks](https://github.com/johanblumenberg/ts-mockito/commit/67195b7317014b6da27b97869efaeb6305d739da)
- [Adding `nextTick()`](https://github.com/johanblumenberg/ts-mockito/pull/2)
- [Better types on `anyFunction()` and `thenCall()`](https://github.com/johanblumenberg/ts-mockito/pull/3)
- [Better call verification error messages](https://github.com/johanblumenberg/ts-mockito/pull/4)
- Mocking of constructors
- Make `.thenResolve()` work for `PromiseLike<T>`
- [Make spying on an object prototype work](https://github.com/johanblumenberg/ts-mockito/commit/5171b536ba52d3e5d34479965fb95880bff0df4b)
- Type safe `objectContaining()`
- [Date matchers](https://github.com/johanblumenberg/ts-mockito/commit/15d35ac7490469d529ccc3665bf09606660c918d)
- [Numberic matchers](https://github.com/johanblumenberg/ts-mockito/commit/32f4f7acab81be7912f676b8cfc8efca26a06937)
### 1.0.36
- [Recursive matching](https://github.com/johanblumenberg/ts-mockito#recursive-matching) ([commit](https://github.com/johanblumenberg/ts-mockito/commit/0dc7eccd0f1c8216a3cceeb4061958d76aa3ce4b))
- [_ as an alias for `anything()`](https://github.com/johanblumenberg/ts-mockito#_-as-an-alias-for-anything) ([commit](https://github.com/johanblumenberg/ts-mockito/commit/b858e72b5c9dc19709497aae635edc3bae3421ae))
### 1.0.37
- [Reset stubs](https://github.com/johanblumenberg/ts-mockito#resetting-mock-stubs) ([commit](https://github.com/johanblumenberg/ts-mockito/commit/430e566d901f6627a740baa3224fa77a97fd403c))
### 1.0.39
- [Add an expect().nothing() call to verify()](https://github.com/johanblumenberg/ts-mockito/commit/f78bd62fea238f1bf735451a33ffbe90f184add4)
### 1.0.40
- [Log mock invocations](#log-mock-invocations)
### 1.0.41
- [Proceed to call original method](#proceed-to-call-the-original-method)
### 1.0.42
- [Improved argument capturing](#capture-by-matching-arguments)
### 1.0.44
- [Match by JSON string](#match-by-json)
- [Custom matchers](#custom-matchers)
### 1.0.46
- [Match Once](#match-once)
## Installation
`npm install /ts-mockito --save-dev`
## Added functionality in this fork
### Verify with timeout
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.
```typescript
let mockedFoo:Foo = mock(Foo);
await verify(mockedFoo.getBar(3)).timeout(1000);
```
### Mocking interfaces
Mocking interfaces works just the same as mocking classes, except you
must use the `imock()` function to create the mock.
```typescript
let mockedFoo:Foo = imock(); // Foo is a typescript interface
when(mockedFoo.getBar(5)).thenReturn('five');
```
It also works for properties.
```typescript
let mockedFoo:Foo = imock(MockPropertyPolicy.StubAsProperty);
when(mockedFoo.bar).thenReturn('five');
```
For interface mocks, you can set the default behaviour for mocked properties that
have no expectations set.
They can behave either as a property, returning null, or as a function, returning a function that returns null, or throw an exception.
```typescript
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
```
### Mocking free functions
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.
```typescript
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();
```
### Mocking constructors
Sometimes you need to mock a constructor and control creation of new objects.
```typescript
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));
```
### Defer resolving promises
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.
```typescript
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
```
### Mock `React` props with `enzyme`
It's possible to mock props for React components when testing using enzyme.
```typescript
let props: Props = imock(MockPropertyPolicy.StubAsProperty);
when(props.text).thenReturn('OK');
when(props.onClick()).thenReturn();
let c = mount(<MyButton {...instance(props)}>);
```
### Recursive matching
``` typescript
// 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();
```
### `_` as an alias for `anything()`
``` typescript
// 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();
```
### Match by JSON
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 partially matching the JSON content.
``` typescript
// 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();
```
### Custom matchers
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.
```typescript
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();
```
### Match Once
Matchers which match only once, such as `thenReturnOnce()`, `thenThrowOnce()`, `thenResolveOnce()`, `thenRejectOnce()`.
These matchers will only match once and are useful for matching once and then falling back to standard behaviour.
```typescript
beforeEach(() => {
when(fetch(_)).thenResolve({ status: 200 });
});
it("getData() should be able to fetch data", async () => {
const result = await getData("https://example.com/data");
expect(result.status).toEqual(200);
verify(fetch(_)).once();
});
it("getData() should retry on 500 errors", async () => {
// Will override the setup in beforeEach() and return a 500 response once
// and then it will fallback to the behaviour specified in beforeEach()
// in the next invocation
when(fetch(_)).thenResolveOnce({ status: 500 });
const result = await getData("https://example.com/data");
expect(result.status).toEqual(200);
verify(fetch(_)).twice();
});
```
## Usage
### Basics
``` typescript
// 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();
```
### Stubbing method calls
``` typescript
// 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));
```
### Stubbing getter value
``` typescript
// 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);
```
### Stubbing property values that have no getters
Syntax is the same as with getter values.
Please note that stubbing properties that don't have getters only works if the [Proxy](http://www.ecma-international.org/ecma-262/6.0/#sec-proxy-objects) object is available (ES6).
### Call count verification
``` typescript
// 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
```
### Call order verification
``` typescript
// 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)
```
### Throwing errors
``` typescript
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'
}
```
### Custom function
You can also stub a method with your own implementation
``` typescript
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));
```
### Resolving / rejecting promises
You can also stub a method to resolve / reject promises
``` typescript
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"));
```
### Resetting mock calls
You can reset just mock call counter
``` typescript
// 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
```
### Resetting mock stubs
Or reset the mock call counter with all stubs
``` typescript
// 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 mocks
Resetting a mock will reset both call count and stub calls.
For a spy it will also reset the original object, removing the spy.
``` typescript
// 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
```
### Capturing method arguments
``` typescript
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...
### Capture by matching arguments
It is also possible to capture arguments of a specific invocation, by providing
matchers in the same way as when using `when()` or `verify()`.
```typescript
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
```
### Recording multiple behaviors
You can set multiple returning values for the same matching values
``` typescript
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
``` typescript
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:
``` typescript
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:
``` typescript
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
```
### Mocking types
You can mock abstract classes
``` typescript
const mockedFoo: SampleAbstractClass = mock(SampleAbstractClass);
const foo: SampleAbstractClass = instance(mockedFoo);
```
You can also mock generic classes, but note that a generic type is just needed by the mock type definition
``` typescript
const mockedFoo: SampleGeneric<SampleInterface> = mock(SampleGeneric);
const foo: SampleGeneric<SampleInterface> = instance(mockedFoo);
```
### Spying on real objects
You can partially mock an existing instance:
``` typescript
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:
``` typescript
const foo = { bar: () => 42 };
const spiedFoo = spy(foo);
foo.bar();
console.log(capture(spiedFoo.bar).last()); // [42]
```
### Log mock invocations
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.
``` typescript
let mockedFoo:Foo = imock({logInvocations: true});
let foo:Foo = instance(mockedFoo);
foo.bar("hello world");
// This will log:
// call: bar("hello world")
```
### Proceed to call the original method
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.
```typescript
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;
});
```
### Improved argument capturing
### Thanks
* 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)