Inorder implementation
This is the PR for #98.
The idea is to make Mock observable so we can register ordered invocations from different mocks.
The mocking_registry helps to get the mocks for comparing.
Features
- [x] Mocks should be observable from an
InOrderobject to register the order of invocations - [x] Trying to observe the same Mock more than once should be fine
- [x] Should work with specs and no-specs Mocks ~~Should work with spy objects (Maybe in an other PR)~~
- [x] Mocks should be detached automatically with a context manager and also when the Queue call is empty
- [x] Verification errors should be meaningful
@kaste Feel free to add elements to this list
I should not use @override annotation since is not supported before Python 3.12 and also use mypy locally first.
Very promising.
For the tests:
-
Don't do the friends things if it only hides the actual usage of the mocks in the tests.
-
Maybe even the setup is limiting here. Sometimes we test a specced mock, sometimes not.
cat = mock()
in_order = InOrder([cat])
cat.meow("HI")
cat.meow("HO")
a)
in_order.verify(cat).meow("HO") # raises
b)
in_order.verify(cat).meow("HI") # passes
in_order.verify(cat).meow("HO") # passes
in_order.verify(cat).meow("HO") # raises, no?
b1)
in_order.verify(cat).meow("HI") # passes
in_order.verify(cat).meow("HO") # passes
in_order.verify(cat).meow("HI") # raises
c)
in_order.verify(cat).meow(...) # passes
in_order.verify(cat).meow(...) # raises
d)
in_order.verify(cat).bark(...) # raises
- implement context manager to "detach" automatically.
cat = mock()
with InOrder([cat]) as in_order:
cat.meow("HI")
cat.meow("HO")
...
- Check the actual error messages in the tests. The most important part is to have good error messages. E.g. user expected bark(...) but next invocation was actually meow("HI").
(I usually do the tests first. E.g what behavior do I even expect for all the cases. Do they all make sense in concerto.)
a)
probably valid and we have two separate "lines" of recorded executions
with InOrder(cat) as a, InOrder(dog) as b:
b)
a = InOrder(cat)
b = InOrder(cat) # raises? already registered
- Don't do the friends things if it only hides the actual usage of the mocks in the tests.
Okay, I'edit the set up to use a function that calls both the interfaces
- Maybe even the setup is limiting here. Sometimes we test a specced mock, sometimes not.
I'll also test non specs mocks
(I usually do the tests first. E.g what behavior do I even expect for all the cases. Do they all make sense in concerto.)
I'll try to implement it in TDD
a = InOrder(cat) b = InOrder(cat) # raises? already registered
I thought that if we use a set as a container for observed subjects we can not have problems with already registered objects and the scope of an InOrder object is just in a single test case
cat = mock()
dog = mock()
with InOrder(cat) as in_order:
dog.bark()
cat.meow()
in_order.verify(dog).bar() # raises
a = InOrder(cat) b = InOrder(cat) # raises? already registered
Yeah, that should probably not raise. Nesting should ideally work, we need to ensure instead that nesting works.
Added a testing library to express better assertions with containers...I'm working in the various steps @kaste Isn't sufficient to use
rye add --dev assertpy
Generally, this project predates any of the modern tools, like rye, uv etc.
Don't do these distracting things here. They don't help you implement the actual thing.
For this new feature, just use pytest and plain asserts. You don't even need to subclass unittest.TestCase. Look at numpy_test.py as an example. Do not write test around in_order.mocks and in_order.ordered_invocations (=remembered_invocations). These are likely implementation details. Users shouldn't need those. Again: Try to use dumb mocks. Inline everything, don't be DRY here.
Already think about using InOrder(*mocks) to support the shorter and easier to write InOrder(mock()).
Generally, this project predates any of the modern tools, like rye, uv etc.
Don't do these distracting things here. They don't help you implement the actual thing.
For this new feature, just use pytest and plain asserts. You don't even need to subclass
unittest.TestCase. Look at numpy_test.py as an example. Do not write test aroundin_order.mocksandin_order.ordered_invocations(=remembered_invocations). These are likely implementation details. Users shouldn't need those. Again: Try to use dumb mocks. Inline everything, don't be DRY here.Already think about using
InOrder(*mocks)to support the shorter and easier to writeInOrder(mock()).
I don't get it, Unit Tests should help me implementing the feature and serves other developers as a sort of documentation. Users should not reads our tests, but the documentation and the example in it (like doc tests). (In an extremis we can provide E2E tests with behave to get the high level functionality of a feature. The assertion library that I wanted was to have better readability in the test cases and to have functions to have better tests while working with containers.
Anyway the repo is your so I'll do what you want. I'll remove the dependency, use dumb mock, inline everything (no setup like xUnit) and write the tests just with documentation purpuse
@kaste Hello, what do you think? I still need to implement the context manager and the unstub.
Do you think that we should support a workflow like this?:
a = mock()
b = mock()
when(a).method().thenReturn("Calling a")
when(b).other_method().thenReturn("Calling b")
in_order: InOrder = InOrder([a,b])
a.method()
b.other_method()
a.method()
in_order.verify(a).method()
in_order.verify(b).other_method()
in_order.verify(a).method()
For now it is not supported.
Best regards
Isn't that the meat of the re-implementation of InOrder?
cat = mock()
dog = mock()
when InOrder(cat, dog) as in_order:
cat.meow()
dog.bark()
cat.meow()
in_order.verify(cat).meow()
in_order.verify(dof).bark()
in_order.verify(cat).meow()
The old InOrder supported only one mock, and your implementation supports proper cross-mock interactions?
I pushed master to update to modern tooling (uv) and generally towards v2. Please try a rebase. E.g. RememberedInvocation | RememberedProxyInvocation is now invocation.RealInvocation.
The old InOrder supported only one mock, and your implementation supports proper cross-mock interactions?
My implementation supports cross-mock interaction like
in_order.verify(cat).meow()
in_order.verify(dof).bark()
but doesn't yet supports an interaction of this type:
in_order.verify(cat).meow()
in_order.verify(dof).bark()
in_order.verify(cat).meow()
Where a mock method is called twice (or more) in different orders.
But this is easy to fix (I think). After checking that the specific instance of a mock is called I delegate to the original implementation of verify with times=1. I think changing it to atleast=1 solves the issue.
I pushed master to update to modern tooling (
uv) and generally towards v2. Please try a rebase. E.g.RememberedInvocation | RememberedProxyInvocationis nowinvocation.RealInvocation.
I'll looking into that
@kaste I' happy with the implementation and tests. Let me know if you need any changes or some documentation to put in the docs with examples (and how you wanted). Best regards
P.S. I would like to help in an other PR with docs (even for all the project) and "fix" spy objects. If you also need help with the CI let me know.
- Also if
verify(cat).say(...)catches all invocations.
What is the purpose of operator ...?
Things I can't do
- Make the sign of
inorder verifythe same asverifybecause i delegate to the original with fixed arguments so changing arguments will lead to unwanted behavior - Things I don't understand from the past review
- What does it take to support the full signature of the original
verifyfunction? ...means: any arguments- What "things" did you not understand?
Difficult to think this through... The standard verify should be supported at least.