Skip to content

johanblumenberg/ts-mockito

 
 

Repository files navigation

@johanblumenberg/ts-mockito

Fork of ts-mockito, which will be kept until the following PRs are accepted, or similar functionality is added to ts-mockito:

1.0.36

1.0.37

1.0.39

1.0.40

1.0.41

1.0.42

1.0.44

Installation

npm install @johanblumenberg/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.

let mockedFoo:Foo = mock(Foo);
await verify(mockedFoo.getBar(3)).timeout(1000);

Mocking interfaces

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

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.

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.

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.

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.

let props: Props = imock(MockPropertyPolicy.StubAsProperty);
when(props.text).thenReturn('OK');
when(props.onClick()).thenReturn();

let c = mount(<MyButton {...instance(props)}>);

Recursive matching

// 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()

// 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 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();

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.

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();

Usage

Basics

// 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

// 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

// 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 Proxy object is available (ES6).

Call count verification

// 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

// 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

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 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));

Resolving / rejecting promises

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"));

Resetting mock calls

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

Resetting mock stubs

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 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.

// 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

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().

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 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

Mocking types

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);

Spying on real objects

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] 

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.

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 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.

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

About

Mocking library for TypeScript

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 99.5%
  • JavaScript 0.5%