Migrate to v3#
Recommended migration from v2:
- Upgrade to
decoy>=2.4.0 <3. - Incrementally migrate to the new API using
decoy.next. - Once all tests are using the v3 syntax, upgrade to
v3and swapdecoy.nextwithdecoy.
Warning
v3 is not yet released. This migration guide will change as v3 is finalized.
Setup#
For an incremental migration, annotate a test's decoy fixture as decoy.next.Decoy to automatically opt-in that test to the preview API.
- from decoy import Decoy
+ from decoy.next import Decoy
def test_when(decoy: Decoy) -> None:
mock = decoy.mock(cls=SomeClass)
- decoy.when(mock.foo("hello")).then_return("world")
+ decoy.when(mock.foo).called_with("hello").then_return("world")
When#
Replace the rehearsal syntax with When.called_with. See the when guide for more details.
- decoy.when(mock("hello")).then_return("world")
+ decoy.when(mock).called_with("hello").then_return("world")
Options#
The ignore_extra_args option is still passed to Decoy.when, not called_with.
- decoy.when(mock("hello"), ignore_extra_args=True).then_return("world")
+ decoy.when(mock, ignore_extra_args=True).called_with("hello").then_return("world")
Verify#
Replace the rehearsal syntax with Verify.called_with. See the verify guide for more details.
- decoy.verify(mock("hello"))
+ decoy.verify(mock).called_with("hello")
Options#
The times and ignore_extra_args options are still passed to Decoy.verify, not called_with.
- decoy.verify(mock("hello"), times=1)
+ decoy.verify(mock, times=1).called_with("hello")
Verify call sequence#
To verify a sequence of calls, call Decoy.verify from a Decoy.verify_order context.
- decoy.verify(
- mock("a"),
- mock("b"),
- mock("c"),
- )
+ decoy.verify_order():
+ decoy.verify(mock).called_with("a")
+ decoy.verify(mock).called_with("b")
+ decoy.verify(mock).called_with("c")
Async mocks#
Using called_with in Decoy v3, it is no longer necessary to add await to when and verify calls for asynchronous mocks.
- decoy.when(await mock("hello")).then_return("world")
+ decoy.when(mock).called_with("hello").then_return("world")
- decoy.verify(await mock("hello"))
+ decoy.verify(mock).called_with("hello")
Matchers#
Matchers have been reworked to be more type-safe and easier to extend. See the Matcher guide for more details.
- from decoy import Decoy, matchers
+ from decoy.next import Decoy, Matcher
| v2 | v3 |
|---|---|
matchers.AnythingOrNone |
Matcher.any |
matchers.HasAttributes |
Matcher.any(attrs=attrs) |
matchers.IsA |
Matcher.any(type=type) |
matchers.DictMatching |
Matcher.contains |
matchers.ListMatching |
Matcher.contains |
matchers.ErrorMatching |
Matcher.error |
matchers.IsNot |
Matcher.is_not |
matchers.Anything |
Matcher.is_not(None) |
matchers.StringMatching |
Matcher.matches |
matchers.ValueCaptor |
Any other matcher; all matchers are now captors |
| Custom matchers | Matcher + match function |
Attributes#
The decoy.prop API has been replaced. See the attributes guide for more details.
When#
Use When.get, When.set, and When.delete, to configure attribute stubs.
- decoy.when(mock.attr).then_return("world")
+ decoy.when(mock.attr).get().then_return("world")
- decoy.when(decoy.prop(mock.attr).set(42)).then_raise(RuntimeError("oh no"))
+ decoy.when(mock.attr).set(42).then_raise(RuntimeError("oh no"))
- decoy.when(decoy.prop(mock.attr).delete()).then_raise(RuntimeError("oh no"))
+ decoy.when(mock.attr).delete().then_raise(RuntimeError("oh no"))
Verify#
To verify attribute set and delete calls, use Verify.set and Verify.delete.
- decoy.verify(decoy.prop(mock.attr).set(42))
+ decoy.verify(mock.attr).set(42)
- decoy.verify(decoy.prop(mock.attr).delete())
+ decoy.verify(mock.attr).delete()
Context managers#
See the context manager guide for more details.
then_enter_with#
Mocking generator context managers has not changed aside from the called_with syntax.
- decoy.when(mock("hello")).then_enter_with("world")
+ decoy.when(mock).called_with("hello").then_enter_with("world")
__enter__ and __exit__#
In v3, __enter__ and __exit__ can still be stubbed to test advanced context manager interactions. However, in v2, to configure a ContextManager mock to only behave a certain way when entered required jumping through hoops. In v3, both when and verify have an is_entered option to only match calls that happen inside the context.
is_entered is compatible with both ContextManagers and AsyncContextManagers.
subject = decoy.mock(cls=MyCoolContextManager)
- def _handle_enter() -> None:
- """Ensure `read` only works if context is entered."""
- decoy.when(subject.read("some_flag")).then_return(True)
-
- def _handle_exit() -> None:
- """Ensure test fails if subject calls `read` after exit."""
- decoy.when(
- subject.read("some_flag")
- ).then_raise(AssertionError("Context manager was exited"))
-
- decoy.when(subject.__enter__()).then_do(_handle_enter)
- decoy.when(subject.__exit__(None, None, None)).then_do(_handle_exit)
+ decoy.when(subject.read, is_entered=True).called_with("some_flag").then_return(True)
with subject:
result = subject.get_config("some_flag")
assert result is True
Other breaking changes#
- The
mypyplugin is no longer needed and will be removed Decoy.mockis more strict about its arguments, and will raiseMockSpecInvalidErrorif passed an invalidspecvalue.IncorrectCallWarninghas been upgraded to an error:SignatureMismatchError.- Some "public" attributes have been removed from error classes.