View Source Chapter 3: Mock Values

In Chapter 2: Patching we covered two kinds of mock values, Callables and Scalars.

There are 5 other kinds of mock values available for use in a test.

cycle-values

Cycle Values

Cycle Values will endlessly cycle through a list of return values.

When a patched function has a Values.Cycle as its mock value, it will provide the first value in the cycle and then move the first value to the end of the cycle on every invocation.

Consider a function patched with cycle([1, 2, 3]) via the following code

patch(Example, :example, cycle([1, 2, 3]))
InvocationCycle Before CallReturn ValueCycle After Call
1[1, 2, 3]1[2, 3, 1]
2[2, 3, 1]2[3, 1, 2]
3[3, 1, 2]3[1, 2, 3]
4[1, 2, 3]1[2, 3, 1]
5[2, 3, 1]2[3, 1, 2]
6[3, 1, 2]3[1, 2, 3]
7[1, 2, 3]1[2, 3, 1]

We could continue the above table forever since the cycle will repeat endlessly. Cycles can contain callable/1,2, raise/1,2 and throw/1 mock values.

We could create a patch that raises a RuntimeError every other call.

patch(Example, :example, cycle([:ok, raises("broken")]))

This can be helpful for testing retry and backoff constructs, a cycle like this is a good simulation of an unreliable network or dependency.

sequence-values

Sequence Values

Sequence values are similar to cycles, but instead of cycling the list is consumed until only one element is remaining. Once the sequence has only a single element remaining, that element will be returned on all subsequent calls.

Consider a function patched with sequence([1, 2, 3]) via the following code

patch(Example, :example, sequence([1, 2, 3]))
InvocationSequence Before CallReturn ValueSequence After Call
1[1, 2, 3]1[2, 3]
2[2, 3]2[3]
3[3]3[3]
4[3]3[3]
5[3]3[3]

We could continue the above table forever since the sequence will continue to return the last value endlessly. Sequences can contain callable/1,2, raise/1,2 and throw/1 mock values.

There is one special behavior of sequence, and that's an empty sequence, which always returns the value nil on every invocation.

If the test author would like to simulate an exhaustable sequence, one that returns a set number of items and then responds to every other call with nil, they can simply add a nil as the last element in the sequence

patch(Example, :example, sequence([1, 2, 3, nil])
InvocationSequence Before CallReturn ValueSequence After Call
1[1, 2, 3, nil]1[2, 3, nil]
2[2, 3, nil]2[3, nil]
3[3, nil]3[nil]
4[nil]nil[nil]
5[nil]nil[nil]

raises-value

Raises Value

When a function can fail by raising an exception we can use raises/1,2 to have the patched function raise.

raise/1 creates a special Values.Callable to be used as a mock value.

This callable ignores the arguments passed in and unconditionally raises a RuntimeError with the given message.

patch(Example, :example, raises("patched"))

assert_raise RuntimeError, "patched", fn ->
  Example.example()
end

raise/2 creates a special Values.Callable to be used as a mock value.

This callable ignores the arguments passed in and unconditionally raises the specified exception with the given attributes.

patch(Example, :example, raises(ArgumentError, message: "patched"))

assert_raise ArgumentError, "patched", fn ->
  Example.example()
end

throws-value

Throws Value

When a function can fail by raising an exception we can use throws/1 to have the patched function throw.

throws/1 creates a special Values.Callable to be used as a mock value.

This callable ignores the arguments passed in and unconditionally throws the given value.

patch(Example, :example, throws(:patched))

assert catch_throw(Example.example()) == :patched