Spy with verify#
A spy is an object that simply records all calls made to it. Use Decoy.verify to make assertions about calls to a spy, after those calls have been made. Asserting that calls happened after the fact is useful for dependencies called solely for their side-effects.
Tip
In general, units that solely produce side-effects are harder to test, typecheck, and maintain than units that return data. If a mocked dependency returns data that is used in your code, you should use when, not verify.
Usage of when and verify with the same mock are mutually exclusive within a test, and will trigger a warning. See the RedundantVerifyWarning guide for more information.
Verify a call#
The verify API is symmetrical with the when API.
- Pass the mock to
Decoy.verify - Assert on the arguments with
Verify.called_with
database = decoy.mock(name="database")
database.remove("some-id") # <-- call to the spy
decoy
.verify(database)
.called_with("some-id")
By default, if Decoy finds any call matching the verify invocation, the call will pass. However, if a matching call is not found, a VerifyError will be raised.
The behavior of verify may be customized with the following options.
| Option | Type | Description |
|---|---|---|
times |
int |
Check for an exact number of calls. |
ignore_extra_args |
bool |
See only specify some arguments. |
is_entered |
bool |
See context manager state. |
Verify a call count#
You can use the optional times argument to specify call count. With times, the call to verify will fail if there is the incorrect number of matching calls.
decoy
.verify(handler.should_be_called_once, times=1)
.called_with("hello")
decoy
.verify(handler.should_be_called_twice, times=2)
.called_with("goodbye")
decoy
.verify(handler.should_be_never_be_called, times=0)
.called_with("fizzbuzz")
Loosen constraints with matchers#
You may loosen rehearsal constraints using Matchers. See the argument matchers guide for more information.
say_hello = decoy.mock(name="say_hello")
say_hello("foobar")
decoy.verify(say_hello).called_with(Matcher.matches("^foo").arg)
with pytest.raises():
decoy.verify(say_hello).called_with(Matcher.matches("^bar").arg)
Verify order of multiple calls#
If your code under test must call several dependencies in order, use Decoy.verify_order. Decoy will search through the list of all calls made to the given mocks and look for a matching ordered call sequence.
with decoy.verify_order():
decoy.verify(handler.first).called_with("hello")
decoy.verify(handler.second).called_with("world")
Only specify some arguments#
If you don't care about some (or any) of the arguments passed to a spy, you can use the ignore_extra_args argument to tell Decoy to only check the arguments you pass.
def log(message: str, meta: Optional[dict] = None) -> None:
...
mock_log = decoy.mock(func=log)
mock_log("hello world", meta={"foo": "bar"})
decoy.verify(log, ignore_extra_args=True).called_with("hello world")
This can be combined with times=0 to say "this dependency was never called," but your typechecker may complain about this:
decoy.verify(do_something, times=0, ignore_extra_args=True).called_with()