mockito-python icon indicating copy to clipboard operation
mockito-python copied to clipboard

Add a method `thenCallRealMethod` to AnswerSelector to call the original method

Open shashankrnr32 opened this issue 4 years ago • 2 comments

Note: Please feel free to close the issue if this has been discussed before in any of the issue.

Currently the AnswerSelector interface provides the following methods to be used with when or when2 statement

  1. thenReturn: To return a mocked return value
  2. thenRaise: To raise a mock exception
  3. thenAnswer: To call a custom method (Eg: Lambdas)

I noticed the original method is stored in the __wrapped__ attribute when the method is stubbed.

https://github.com/kaste/mockito-python/blob/8e1771c5bb1c3bc5948c167c81768d2380c11c3b/mockito/mocking.py#L122

This issue is to provide another option thenCallRealMethod to AnswerSelector to call this original method instead of mocking a stub value so that the original method get called instead of the mock. This will support "Partial Mocking" of these methods by allowing the user to call the original method in case the original method is not modifiable.

I have a small work here to support this (untested for now)

(I don't need this as such for my usecase, just some improvement!.)

Example:

class Dog(object):
    def bark(self, sound):
        return "%s!" % sound

rex = Dog()
when(rex).bark('Miau').thenReturn('Wuff').thenCallRealMethod().thenReturn('Puff')

assert 'Wuff' == rex.bark('Miau')
assert "Miau!" == rex.bark("Miau")     # Calls the original method
assert 'Puff' == rex.bark('Miau')

Also see: https://javadoc.io/static/org.mockito/mockito-core/4.1.0/org/mockito/stubbing/OngoingStubbing.html#thenCallRealMethod--

Thank you.

shashankrnr32 avatar Dec 09 '21 20:12 shashankrnr32

Yes, I wanted to do that at some point. I don't know a good name. For thenCallRealMethod, I don't like the method in it as we're not restricted to methods in any way in Python. We generally work with callables. For example os.path.exists is not a classical method.

For the implementation, look how RememberedProxyInvocation does that for spy: https://github.com/kaste/mockito-python/blob/27e5587ac683262aa883bcb48130ed1a974df517/mockito/invocation.py#L144-L151

Generally, I think when(rex).bark('Miau').thenReturn('Wuff').thenCallRealMethod().thenReturn('Puff') is a very strange example. A bit more typical is for example to patch os.path.exists where you often want the original implementation intact except for the very call in your unittest.

If I remember correctly, the way to do this right now is:

spy2(os.path.exists)
when(os.path).exists("/foo/bar").thenReturn(True)

which does the trick but is not very accessible for users. The idea behind thenCallOriginalImplementation is to make this work like so:

when(os.path).exists(...).thenCallOriginalImplementation()
when(os.path).exists("/foo/bar").thenReturn(True)

Here when(os.path).exists(...).thenCallOriginalImplementation() is completely the same as and interchangeable with spy2(os.path.exists). T.i. we could reduce the API surface by deprecating spy2 and just relying on the one user endpoint when/2.

Can you come up with some real examples? when(emailService).send().thenReturn(True).thenCallThrough().thenReturn(False) doesn't sound correct.

kaste avatar Dec 10 '21 10:12 kaste

Agreed that

when(rex).bark('Miau').thenReturn('Wuff').thenCallRealMethod().thenReturn('Puff')

may not be the best way of using it, but it could be done (For whatever reasons!)

One simple example I can think of is of a method making 2 API calls, one to an external endpoint and another to the localhost.

def my_method():
    requests.get("https://unknownhost.com/path")
    requests.get("https://localhost:4443/path")

For the above tested method, my expectation would be that the invocation return a stubbed value when the request is made to External endpoint while the call may be made to the server running locally. I know that this is not a "Ideal" usecase that this may be used, but something similar might be the usecase in different scenarios.

My test method would be

when(requests).get("https://unknownhost.com/path").thenReturn(mock())
when(requests).get(...).thenCallOriginalImpl()

I will add more examples if I think of anymore!

shashankrnr32 avatar Dec 10 '21 16:12 shashankrnr32

This has been implemented by @avandierast and released.

kaste avatar Aug 30 '22 23:08 kaste