Mocking

Aaron Jacobs edited this page Aug 26, 2014 · 4 revisions

Google JS Test has a capable mocking system built in and accessible from any test. It is modeled on Google's C++ mocking framework, and should be familiar for anybody who has used that tool before, but can easily be picked up by beginners too.

Creating mocks

Create mock functions using createMockFunction:

var func = createMockFunction();

Create an instance of MyClass with every function in its prototype replaced by a mock function using createMockInstance:

var instance = createMockInstance(MyClass);

Setting up expectations

Expect a call to a mock function func to be made with particular arguments like in the following examples:

// func should be called once with the argument 'taco'.
expectCall(func)('taco');

// Func should be called twice, each time with an arugment matching the given
// regular expression.
expectCall(func)(containsRegExp(/nice.*burrito/))
    .times(2);

These expectations are automatically verified at the end of the currently running test, and will cause it to fail if they are not satisfied. Additionally, it will fail if a call not matching a registered expectation is received, or if a registered expectation is matched too many times.

Note the following:

  • If you give a value instead of a matcher, an implicit equals matcher is used, as with the 'taco' argument.

  • If you don't use a .times() expression, the expectation has an implicit cardinality of one. (Actions can affect this; see below.)

Configuring actions

By default a mock function does nothing and returns nothing (i.e. undefined) when it is called. You can add actions that will be performed when an expectation is matched as in the following example:

expectCall(func)('taco')
    .willOnce(returnWith(2));

expectCall(func)('burrito')
    .willOnce(function() { [...] })
    .willOnce(function() { [...] })
    .willRepeatedly(returnWith(3));

expectCall(func)('queso')
    .times(2)
    .willRepeatedly(returnWith(4));

func('burrito');  // Invokes first function above
func('burrito');  // Invokes second function above
func('taco');     // Returns 2
func('taco');     // Causes unexpected call failure
func('burrito');  // Returns 3
func('burrito');  // Returns 3

func('queso');    // Returns 4
func('queso');    // Returns 4
func('queso');    // Causes unexpected call failure

As demonstrated above, the following modifiers are available:

  • times(n) – Expect that this call will be matched exactly n times.

  • willOnce(f) – Set up an action f that is only performed once. You may list multiple such actions, and they are performed in order as the mock function is called.

  • willRepeatedly(f) – Set up a fallback action f that is invoked repeatedly after any .willOnce() actions have been exhausted.

An action is simply a function. You may used the built-in returnWith and doAll, or supply your own function. The function is passed the arguments given to the mock function, and whatever it returns (if anything) is returned by the mock function.

The rules for cardinality with regard to actions are as follows:

  1. If an explicit cardinality N is set with .times(N), then anything other than N matching calls will cause a test failure.

  2. Otherwise, if there are any one-time actions set up, then it is expected there will be at least that many matching calls. If there is not also a fallback action, then it is expected that there will be exactly that many.

  3. Otherwise, if there is a fallback action, any number of matching calls (including zero) is allowed.

  4. Otherwise, the implicit expected count is one.

Expectation order

When a mock function receives a call, its expectations are evaluated from most recent to least recent. The idea is that at a beginning of a test, you can set up more general or default actions, and later you can set more specific ones relevant to the current test alone. For example:

expectCall(func)(_)
    .willRepeatedly(returnWith(2));

expectCall(func)('taco')
    .willOnce(returnWith(3))
    .willOnce(returnWith(5));

func('burrito');   // Returns 2
func('taco'));     // Returns 3
func('taco'));     // Returns 5
func('taco'));     // Causes 'too many calls' failure; the most recent match is already exhausted