-
-
Notifications
You must be signed in to change notification settings - Fork 13
Inorder implementation #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
I should not use |
Very promising. For the tests:
|
(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.) |
|
Okay, I'edit the set up to use a function that calls both the interfaces
I'll also test non specs mocks
I'll try to implement it in TDD
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 |
Observing the same mock is not added in the InOrder list
|
||
def remember(self, invocation): | ||
self.invocations.append(invocation) | ||
self.notify() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it reads much better when we could do, for each observer. oberserver.notify(invocation). This is semantically equivalent to: for each observer. observer.remember(invocation). As if the invocations
here are a specialized variant of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll look in to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still: for each observer. observer.remember(invocation)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
☝️
|
Yeah, that should probably not raise. Nesting should ideally work, we need to ensure instead that nesting works. |
returns the name of the mocked class if specs is set, Dummy Mock otherwise
Added a testing library to express better assertions with containers...I'm working in the various steps 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 Already think about using |
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. 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 |
tests/in_order_test.py
Outdated
a.method() | ||
b.other_method() | ||
|
||
with pytest.raises(VerificationError) as e: | ||
in_order.verify(a).method() | ||
in_order.verify(b).other_method() | ||
assert str(e.value) == (f"Trying to verify ordered invocation " | ||
f"of {b}, " | ||
f"but no other invocations have been recorded.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is likely what to_ignore
is for. The error message is wrong because b
is not even registered with the InOrder. verifying a
is not necessary you just test for unknown/unregistered/unexpected mocks.
tests/in_order_test.py
Outdated
with pytest.raises(VerificationError) as e: | ||
with InOrder([a, b]) as in_order: | ||
a.method() | ||
b.other_method() | ||
|
||
in_order.verify(a) | ||
assert str(e.value) == (f"Some mocks have not been verified: " | ||
f"{in_order.ordered_invocations}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't raise actually. On contraire, we just detach the mocks. Test that verify
works after leaving the context manager and (b) that using the mock after detach doesn't get recorded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of stuff.
- Generally, I want this
verify
to accept all keyword arguments as the original verify. - I actually had a lot of test cases in my first comment, and I don't see any of them here. Esp. you completely forgot to test different arguments. Like cat.make("meow"), cat.make("rrr") and if they can be verified. Also if
verify(cat).say(...)
catches all invocations. - The error messages should be more like e.g.
You expected
cat.meow()
but next remembered invocation is
dog.bark()
This is especially hard to do in a helpful way as we don't easily get the names cat
and dog
. I don't think you should expose the internal Mocks at least as these are also implementation details, in fact proxy objects. The user doesn't see them but only the mocked (wrapped) object.
tests/in_order_test.py
Outdated
f"Called {mock_registry.mock_for(a)}, " | ||
f"but expected {mock_registry.mock_for(b)}!") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To get a feeling for the error messages, these should be typed out. mock_for
returns an internal helper anyway. So that must rather be You expected an invocation on b but the next invocation comes from a.
Already defined in invocation.py
What is the purpose of operator |
Things I can't do
|
""" | ||
|
||
if not (mock in self.mocks): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if mock not in self.mocks:
raise VerificationError( | ||
f"InOrder verification error! " | ||
f"Wanted a call from {str(expected_mock)}, but " | ||
f"got {invocation} from {str(called_mock)} instead!" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asked you to not expose our internal Mock
class. Users only care about their mocks and actually their real classes. (Actually most of the time, user just patch their real classes and don't use "mocks" at all.) If you have a Mock
, most likely the user is interested in its mocked_obj
and/or its spec
. Our "Mock" has a proxy relation to the mocked object.
def __str__(self): | ||
name: str = 'Dummy' | ||
if self.spec: | ||
name = self.spec.__name__ | ||
return f"Mock<{name}>" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likely not needed as Mock
is an internal class, the standard repr
implementation Python provides for free was always enough.
def __str__(self): | ||
name = 'Dummy' | ||
if spec: | ||
name = spec.__name__ | ||
return f"Mock<{name}>" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't override the __repr__
just above that. Why is that useful?
with pytest.raises(ValueError) as e: | ||
InOrder(a, a) | ||
assert str(e.value) == ("The following Mocks are duplicated: " | ||
"['Mock<Dummy>']") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here you see that your new str implementation for mock
isn't helpful as it is basically a static string for all dumb mocks.
"Wanted a call from Mock<Dummy>, but got " | ||
"bark() from Mock<Dummy> instead!" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These come from the Mock.__str__
implementation. Please note that this error message is not helpful for the end-user at all.
Ideally maybe
Wanted but not invoked cat.meow()
Next recorded invocation is: dog.bark()
Is that too tricky?
Then maybe
Wanted a call from ...
Next recorded invocation is ...bark()
That shouldn't be that hard.
def test_exiting_context_manager_should_detatch_mocks(): | ||
cat = mock() | ||
dog = mock() | ||
|
||
with InOrder(cat, dog) as in_order: | ||
cat.meow() | ||
dog.bark() | ||
|
||
in_order.verify(cat).meow() | ||
in_order.verify(dog).bark() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The important thing is that invocation on cat do not get recorded after detach.
if not self.ordered_invocations: | ||
raise VerificationError( | ||
f"Trying to verify ordered invocation of {mock}, " | ||
f"but no other invocations have been recorded." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why "no other invocations".
Maybe
Wanted a call on ...
But nothing has been recorded yet.
expected_mock = mock_registry.mock_for(mock) | ||
if called_mock != expected_mock: | ||
raise VerificationError( | ||
f"InOrder verification error! " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally, do not start with "InOrder verification error!" but just a newline "\n".
f"Trying to verify ordered invocation of {mock}, " | ||
f"but no other invocations have been recorded." | ||
) | ||
ordered_invocation = self.ordered_invocations.popleft() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of popping them you can maybe take the next invocation that is still not .verified
. IIRC verify(_main) sets that.
|
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
InOrder
object to register the order of invocationsShould work with spy objects (Maybe in an other PR)@kaste Feel free to add elements to this list