Using mocks

Creating a mock instance

Code generation creates a newMoqXXX factory function for each mock you generate. Simply invoke the function and hold on to the new mock for further testing:

isFavMoq := newMoqIsFavorite(scene, nil)
writerMoq := newMoqWriter(scene, nil)

You might be curious what that scene parameter is. A scene provides an abstraction on a collection of mocks. It allows your tests to control all of its mocks at once. There are more details on the use of scenes below but for now, you can create a scene like this:

scene := moq.NewScene(t)

Expectations

To get a mock to perform specific behaviors, you have to tell it what to expect and how to behave. For function mocks, the onCall function (generated for you) has the same parameter signature as the function itself. The return value of the onCall function is a type that (via its returnResults method) informs the mock what to return when invoked with the given set of parameters. For our IsFavorite function mock, we tell it to expect to be called with parameters 1, 2 and then 3 but only 3 is our favorite number like so:

isFavMoq.onCall(1).returnResults(false)
isFavMoq.onCall(2).returnResults(false)
isFavMoq.onCall(3).returnResults(true)

Working with interface mocks is very similar to working with function mocks. For interface mocks, the generated onCall method returns the expectation recorder of the mocked interface (a full implementation of the interface for recording expectations). For our Writer mock example, we tell it to expect a call to Write with the following code:

writerMoq.onCall().Write([]byte("3")).
    returnResults(1, nil)

Note in the above code, we told the mock to return 1 and nil with a call to the generated returnResults method. Per the interface definition of a writer, we wrote one byte successfully with no errors. Alternatively, we could indicate an error with a small change:

writerMoq.onCall().Write([]byte("3")).
    returnResults(0, fmt.Errorf("couldn't write"))

Arbitrary (any) parameters

Sometimes it’s hard to know what exactly the parameter values will be when setting expectations. You want to say “ignore this parameter” when setting some expectations. The generated any function does exactly that — the specified parameter will be ignored (in the recorded expectation and again later when the mock is invoked). The following code sets the expectation for a function called GadgetsByWidgetId that takes a single int parameter called widgetId. With this expectation, the mock will return the same result regardless of the value of widgetId:

storeMoq.onCall().GadgetsByWidgetId(0).any().widgetId().
    returnResults(nil, nil).repeat(moq.Times(2))

Expectations with more matching parameters are given precedence over expectations with fewer matching parameters. In another test, we work with another mocked method called LightGadgetsByWidgetId that presumably returns gadgets associated with a specified widget that are less than a specified weight. The following snippet returns the g1 and g2 gadgets when LightGadgetsByWidgetId is called with a widgetId of 42 regardless of the value specified for maxWeight:

storeMoq.onCall().LightGadgetsByWidgetId(42, 0).any().maxWeight().
    returnResults([]demo.Gadget{g1, g2}, nil)

In the same test, these lines return g3 and g4 regardless of either parameter specified to LightGadgetsByWidgetId:

storeMoq.onCall().LightGadgetsByWidgetId(0, 0).
	any().widgetId().any().maxWeight().
    returnResults([]demo.Gadget{g3, g4}, nil)

Callers will be returned g3 and g4 unless the widgetId is 42, in which case they will be returned g1 and g2.

Parameter indexing

Each expectation is indexed by its parameters. Moqueries has two indexing mechanisms: indexing by value and indexing by hash. Indexing by value simply places the parameter value into the parameters key — a structure used to store and retrieve expectations. Indexing by hash first performs a deep hash on a given parameter value and instead stores values hash in the parameters key.

Indexing by value is preferred but there are cases that can’t use indexing by value. For instance, if a slice is used as a parameter (map) key, Go would panic (or just fail to compile). Conversely, there are occasions where indexing by hash is preferred. Perhaps your test doesn’t have access to the exact value used in the production code but your test code can make an identical instance — one that will have an identical hash value.

The parameter indexing for a given parameter is determined by the following rules:

  1. Builtin types (except for the error interface) are indexed by value.
  2. Arrays (with a specified length) containing builtin types are indexed by value.
  3. Structures (including structures within structures) containing builtin types are indexed by value.
  4. Any composition of rules #1 through #3 (structures containing arrays or arrays containing structures all containing builtin types) are indexed by value.
  5. Slices, maps and ellipses (...) parameters are indexed by hash.
  6. References and interfaces are indexed by hash.

All the above rules can be overridden except for #5 — as mentioned above, indexing by value here would cause a panic. To change the indexing mechanism for a given parameter, use the runtime.parameterIndexing configuration:

storeMoq.runtime.parameterIndexing.LightGadgetsByWidgetId.widgetId = moq.ParamIndexByHash

Repeated results

When expectations need to be returned repeatedly, the repeat function can be called with a list of repeaters. Some examples of repeaters are Times and AnyTimes can be used to control how often a particular result is returned. For instance, the following code instructs the mock function to return false five times and then true one time (one time is the default):

isFavMoq.onCall(7).
    returnResults(false).repeat(moq.Times(5)).
    returnResults(true)

AnyTimes instructs the mock to repeatedly return the same values regardless of how many times the function is called with the given parameters. Note that AnyTimes can only be used once for a given set of parameters.

Times and AnyTimes can be used together as well. This code returns true twice and then always returns false regardless of how many times the function is called with the parameter 7:

isFavMoq.onCall(7).
    returnResults(true).repeat(moq.Times(2)).
    returnResults(false).repeat(moq.AnyTimes())

Using MinTimes and/or MaxTimes, you can assert a minimum number, maximum number or range (min and max) of calls were made:

isFavMoq.onCall(1).
    returnResults(false).
    repeat(moq.MaxTimes(3))
isFavMoq.onCall(2).
    returnResults(false).
    repeat(moq.MinTimes(2))
isFavMoq.onCall(3).
    returnResults(true).
    repeat(moq.MinTimes(1), moq.MaxTimes(3))

Optional can be used to indicate that none of the calls are required (the equivalent of MinTimes(0)). Optional can be called by itself (with MaxTimes(1) assumed), or with an explicit call to MaxTimes or with an explicit call to Times:

isFavMoq.onCall(0).
    returnResults(false).
    repeat(moq.Optional())
isFavMoq.onCall(1).
    returnResults(false).
    repeat(moq.Optional(), moq.MaxTimes(3))
isFavMoq.onCall(2).
    returnResults(false).
    repeat(moq.MinTimes(2))
isFavMoq.onCall(4).
    returnResults(false).
    repeat(moq.Optional(), moq.Times(3))

Note that some repeated result combinations are not supported and will cause a test failure during setup. For instance, specifying that a call should be made MinTimes(3) and Optional is not allowed.

Asserting call sequences

Some test writers want to make sure not only were certain calls made but also that the calls were made in an exact order. If you want to assert that all calls for a test are to be in order, just set the mock’s default to use sequences on all calls via the Config value:

config := moq.Config{Sequence: moq.SeqDefaultOn}

Now the calls to all mocks using the above config must be in the exact sequence. The sequence of expectations must match the sequence of calls to the mock.

If there are just a few calls that must be in a specific order relative to each other, call the seq method when recording expectations:

isFavMoq.onCall(1).seq().returnResults(false)
isFavMoq.onCall(2).seq().returnResults(false)
isFavMoq.onCall(3).seq().returnResults(true)

This is basically overriding the default so that just the calls specified use a sequence. You can also turn off sequences on a per-call basis when the default is to use sequences on all calls using the noSeq method:

writerMoq.onCall().Write([]byte("3")).noSeq().
    returnResults(1, nil)

Do functions

Sometimes you need to tap into what your mock is doing. You may need to capture a value that was passed to a mock, or you may need to have some logic calculate what a mock’s response should be. Do functions do just that. If you just need to listen in to a returnResults expectation, you provide a function that matches the mocked functions parameters (in this case the mocked function takes a single int parameter):

sum := 0
sumFn := func(n int) {
    sum += n
}

Then chain an andDo call after the returnResults call:

isFavMoq.onCall(1).returnResults(false).andDo(sumFn)
isFavMoq.onCall(2).returnResults(false).andDo(sumFn)
isFavMoq.onCall(3).returnResults(true).andDo(sumFn)

If on the other hand you need to calculate a mock’s return values, start with a function that has the same signature as the mocked function (both parameters and result values):

isFavFn := func(n int) bool {
    return n%2 == 0
}

Now you can replace both the returnResults and andDo calls with a single call to doReturnResults:

isFavMoq.onCall(0).any().n().
    doReturnResults(isFavFn).repeat(moq.AnyTimes())

Note this expectation will return the calculated value (n%2 == 0) regardless of the input parameters and regardless of how may times it is invoked.

Passing the mock to production code

Each mock gets a generated mock method. This function accesses the implementation of the interface or function invoked by production code. In our example, we have a type called FavWriter that needs an IsFavorite function and a Writer:

d := demo.FavWriter{
    IsFav: isFavMoq.mock(),
    W:     writerMoq.mock(),
}

Nice vs. Strict

Sometimes your mocks will get lots of function calls with lots of different parameters — maybe more calls than you can (or want) to configure. Nice mocks trigger special logic that allow them to return zero values for any unexpected calls. Creating a nice mock is as simple as supplying a little configuration to the new method (the value was nil above which defaults to creating strict mocks):

isFavMoq := newMoqIsFavorite(
    scene, &moq.Config{Expectation: moq.Nice})

Now we only need to set expectations when returning non-zero values (returnResults(false) is now the default):

isFavMoq.onCall(3).returnResults(true)

Calling this mock with any value besides 3 will now return false (without having to register any other expectations).

Asserting all expectations are met

After your test runs, you may want to verify that all of your expectations were actually met. Each mock implements AssertExpectationsMet to do just that:

writerMoq.AssertExpectationsMet()

Resetting the state

Occasionally you need reset the state of a mock. Re-creating the mock is preferred but there are situations where that isn’t possible (maybe a long-running test, or the mock has already been handed off to other code). In any case, calling Reset does just that — it resets the mock:

writerMoq.Reset()

Working with multiple mocks

Quite often tests will require several mocks. A Scene is a collection of mocks, and it allows you to perform actions on all the mocks with a single call. Both AssertExpectationsMet and Reset are supported:

scene.AssertExpectationsMet()