Patch.Mock.Value (patch v0.8.1) View Source

Interface for generating mock values.

In a test this module is imported into the test and so using this module directly is not necessary.

Link to this section Summary

Functions

Advances the given value.

Create a new Values.Callable to be used as the mock value.

Create a new Values.Cycle to be used as the mock value.

Guard that checks whether a value is a proper Values module

Generate the next return value and advance the underlying value.

Creates a special Values.Callable to be used as a mock value.

Creates a special Values.Callable to be used as a mock value.

Creates a new Values.Scalar to be used as the mock value.

Creates a new Values.Sequence to be used as a mock value.

Creates a special Values.Callable to be used as a mock value.

Link to this section Types

Link to this section Functions

Specs

advance(value :: t()) :: t()

Advances the given value.

Sequences and Cycles both have meaningful advances, all other values types this acts as a no-op.

Link to this function

callable(target, dispatch \\ :apply)

View Source

Specs

Create a new Values.Callable to be used as the mock value.

When a patched function has a Values.Callable as its mock value, it will invoke the callable with the arguments to the patched function on every invocation to generate a new value to return.

patch(Example, :example, callable(fn arg -> {:patched, arg} end))

assert Example.example(1) == {:patched, 1}   # passes
assert Example.example(2) == {:patched, 2}   # passes
assert Example.example(3) == {:patched, 3}   # passes

Any function literal will automatically be promoted into a Values.Callable unless it is wrapped in a scalar/1 call.

patch(Example, :example, fn arg -> {:patched, arg} end)

assert Example.example(1) == {:patched, 1}   # passes
assert Example.example(2) == {:patched, 2}   # passes
assert Example.example(3) == {:patched, 3}   # passes

callable/2 allows the test author to provide a dispatch_mode of either :apply or :list.

When :apply is used the function is called with the same arity of the patched function. When :list is used the function is always called with a single argument, a list of arguments to the patched function.

patch(Example, :example, callable(fn a, b, c -> {:patched, a, b, c} end), :apply)

assert Example.example(1, 2, 3)  == {:patched, 1, 2, 3}   # passes

assert_raise BadArityError, fn ->
  Example.example(:test)
end

Compare this with using list dispatch

patch(Example, :example, callable(fn
  [a, b, c] ->
    {:patched, a, b, c}

  [a] ->
    {:patched, a}
end, :list))

assert Example.example(1, 2, 3) == {:patched, 1, 2, 3}   # passes
assert Example.example(1) == {:patched, 1}   # passes

When multiple arity support is needed, use :list dispatch.

Specs

cycle(values :: [term()]) :: Patch.Mock.Values.Cycle.t()

Create a new Values.Cycle to be used as the mock value.

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.

Link to this macro

is_value(module)

View Source (macro)

Guard that checks whether a value is a proper Values module

Specs

next(value :: t(), arguments :: [term()]) :: {t(), term()}

Generate the next return value and advance the underlying value.

Specs

raises(message :: String.t()) :: Patch.Mock.Values.Callable.t()

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
Link to this function

raises(exception, attributes)

View Source

Specs

raises(exception :: module(), attributes :: Keyword.t()) ::
  Patch.Mock.Values.Callable.t()

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

Specs

scalar(value :: term()) :: Patch.Mock.Values.Scalar.t()

Creates a new Values.Scalar to be used as the mock value.

When a patched function has a Values.Scalar as its mock value, it will provide the scalar value on every invocation

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

assert Example.example() == :patched   # passes
assert Example.example() == :patched   # passes
assert Example.example() == :patched   # passes

When patching with any term that isn't a function, it will automatically be promoted into a Values.Scalar.

patch(Example, :example, :patched)

assert Example.example() == :patched   # passes
assert Example.example() == :patched   # passes
assert Example.example() == :patched   # passes

Since functions are always automatically promoted to Values.Callable, if a function is meant as a scalar value it must be wrapped in a call to scalar/1.

patch(Example, :get_name_normalizer, scalar(&String.downcase/1))

assert Example.get_name_normalizer == &String.downcase/1   # passes

Specs

sequence(values :: [term()]) :: Patch.Mock.Values.Sequence.t()

Creates a new Values.Sequence to be used as a mock value.

When a patched function has a Values.Sequence as its mock value, it will provide the first value in the sequence as the return value and then discard the first value. Once the sequence is down to a final value it will be retained and returned on every subsequent invocation.

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]

Specs

throws(value :: term()) :: Patch.Mock.Values.Callable.t()

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