View Source Patch.Mock.Value (patch v0.16.0)
Interface for generating mock values.
In a test this module is imported into the test and so using this module directly is not necessary.
Summary
Functions
Advances the given value.
Create a new Values.Callable to be used as the mock value.
callable/2 allows the test author to provide additional configuration.
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 value that raises a RuntimeError with the given message.
Creates a special value that raises the given exception with the provided attributes.
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 that throws the provided value when evaluated.
Types
@type t() :: Patch.Mock.Values.Callable.t() | Patch.Mock.Values.CallableStack.t() | Patch.Mock.Values.Cycle.t() | Patch.Mock.Values.Raises.t() | Patch.Mock.Values.Scalar.t() | Patch.Mock.Values.Sequence.t() | Patch.Mock.Values.Throws.t() | term()
Functions
Advances the given value.
Sequences and Cycles both have meaningful advances, all other values types this acts as a no-op.
@spec callable(target :: function()) :: Patch.Mock.Values.Callable.t()
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} # passesAny function literal will automatically be promoted into a Values.Callable unless it is
wrapped in a scalar/1 call.
See callable/2 for more configuration options. callable/1 calls use the default
configuration options
- dispatch: :apply
- evaluate: :passthrough
@spec callable( target :: function(), dispatch :: Patch.Mock.Values.Callable.dispatch_mode() ) :: Patch.Mock.Values.Callable.t()
@spec callable(target :: function(), options :: [Patch.Mock.Values.Callable.option()]) :: Patch.Mock.Values.Callable.t()
callable/2 allows the test author to provide additional configuration.
There are two options
:dispatch
Controls how the arguments are dispatched to the callable.
:applyis the default. It will call the function with the same arity as the incoming call.:listwill always call the callable with a single argument, a list of all the incoming arguments.
Apply Example
patch(Example, :example, callable(fn a, b, c -> {:patched, a, b, c} end), :apply)
assert Example.example(1, 2, 3) == {:patched, 1, 2, 3} # passesList Example
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
:evaluate
Controls how the callable is evaluated.
:passthroughis the default. It will passthrough to the original function if the provided callable fails to pattern match to the incoming call:strictwill bubble up anyBadArityErrororFunctionClauseErrors.
Legacy Configuration Behavior (may be deprecated)
This function accepts either a single atom, in which case it will assign that to the :dispatch
configuration and use the default :evaluate option.
The following calls are equivalent
# Using legacy configuration convention
patch(Example, :example, callable(fn args -> {:patched, args}, :apply))
# Using explicit options without evaluate
patch(Example, :example, callable(fn args -> {:patched, args}, dispatch: :apply))
# Using fully specified explicit options
patch(Example, :example, callable(fn args -> {:patched, args}, dispatch: :apply, evaluate: :passthrough))Multiple Arities
dispatch: :list used to be the preferred way to deal with multiple arities, here's an example.
patch(Example, :example, callable(fn
[a] ->
{:patched, a}
[a, b, c] ->
{:patched, a, b, c}
end, dispatch: :list))
assert Example.example(1) == {:patched, 1}
assert Example.example(1, 2, 3) == {:patched, 1, 2, 3}Patch now has "Stacked Callables" so the preferred method is to use the equivalent code
patch(Example, :example, fn a -> {:patched, a} end)
patch(Example, :example, fn a, b, c -> {:patched, a, b, c} end)
assert Example.example(1) == {:patched, 1}
assert Example.example(1, 2, 3) == {:patched, 1, 2, 3}
@spec 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]))| Invocation | Cycle Before Call | Return Value | Cycle 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.
Guard that checks whether a value is a proper Values module
Generate the next return value and advance the underlying value.
@spec raises(message :: String.t()) :: Patch.Mock.Values.Raises.t()
Creates a special value that raises a RuntimeError with the given message.
patch(Example, :example, raises("patched"))
assert_raise RuntimeError, "patched", fn ->
Example.example()
end
@spec raises(exception :: module(), attributes :: Keyword.t()) :: Patch.Mock.Values.Raises.t()
Creates a special value that raises the given exception with the provided attributes.
patch(Example, :example, raises(ArgumentError, message: "patched"))
assert_raise ArgumentError, "patched", fn ->
Example.example()
end
@spec 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 # passesWhen 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 # passesSince 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
@spec 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]))| Invocation | Sequence Before Call | Return Value | Sequence 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])| Invocation | Sequence Before Call | Return Value | Sequence 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] |
@spec throws(value :: term()) :: Patch.Mock.Values.Throws.t()
Creates a special values that throws the provided value when evaluated.
patch(Example, :example, throws(:patched))
assert catch_throw(Example.example()) == :patched