kaste / mockito-python

Mockito is a spying framework
MIT License
123 stars 12 forks source link

Getting the mocked obj instance in the mock method #48

Closed avandierast closed 2 years ago

avandierast commented 2 years ago

Hello ! First, I really like mockito-python :heart:

Now my problem :) If I want to mock the method of a class with thenAnswer and then have access to the instance self inside the stub function. I've tried different ways but I don't find any way to do so:

from mockito import when
class A:
    def __init__(self, a):
        self.a = a

    def method(self):
        print(self.a)

def fake_method(self_A):
    print("fake:", self_A.a)

when(A).method().thenAnswer(fake_method)
A(10).method()
# > TypeError: fake_method() missing 1 required positional argument: 'self_A'

With pytest.monkeypatch or pytest-mock, I usualy use functools.partialmethod to do that, but mockito tries to call directly the partialmethod and fails.

I've looked inside and the culprit seems to be the throw-away of self/cls in new_mocked_method so I think there is no way to get self in the mock.

Thank for the help. I'm open to do a PR but I failed to make it works easily....

kaste commented 2 years ago

That is by design. Initially thenAnswer was used internally and then exposed later*. Typically it makes writing answer-functions easier.

What's the use case here? Ideally answers from stubs/mocks are constants or sort of hard-coded.

* It is actually introduced by #1 and tries to mimic the java library

avandierast commented 2 years ago

I try to make a method fail in some cases, and only once. The solution i tired to implement was to thenAnswer the original method after a first mock that fails. I've seen the other ticket about thenCallRealMethod that would be really usefull. Currently, I've implemented my partial failing mock with classic mocking.

kaste commented 2 years ago

That sounds like the other way around from your original posting because you want to call through to the original code. (In the OP you defined a function fake_method on the module level.)

There seems to be a bug or limitation here in that

> spy2(A.method)
> A().method()

also throws for the very same reason.

It works when you patch the instance though. E.g.

> a = A()
> spy2(a.foo)
> a.foo()

Not sure what to make with this. Do you need to patch A t.i. you don't have control over the instantiation? Example code would help probably.

avandierast commented 2 years ago

Ok, I will mix up my answers to be specific :)

I need to mock a function but only once. For instance, if there was thenCallRealMethod, I would do:

from mockito import when

class A:
    def method(self):
        print(self)

when(A).method().thenRaise(Exception).thenCallRealMethod()
with retry():
    A().method()

That's not true unit-tests with mocks. It is for integration tests but in non-nominal cases to check the robustsness of the code. The expressiveness of mockito would be really usefull even in those cases :)

kaste commented 2 years ago

An implementation of thenCallOriginalImplementation is welcome. Of course, the example, if you test a retry-er you really want mocked/stubbed answers. A retry-er is generic.

For the code you gave, as I said, it at least works with instances.

a = A()
detached_method = a.method
when(a).method().thenRaise(ValueError).thenAnswer(detached_method).thenReturn("mocked")

This would be more convenient if we also had the reverse API. E.g.

doAnswer(a.method).when(a).method()

as we only need the detached_method trick because a.method gets patched before thenAnswer is evaluated.

That being said, I'm interested in the thenCallOriginalImplementation thing.

kaste commented 2 years ago

Ah, we can close this as you fixed it.