4

I'm using unittest.mock, this wonderful library. However I was surprised by an unexpected behavior, and I don't see an obvious solution. I'm using it for my unit tests and it is vital to understand perfectly how it behaves to have useful tests.

I know the code below in show_bar is broken, it is calling a classmethod instead of the instance method. However, all my mock unittests are passing :

Code containing a bug:

class Foo(object):
    def bar(self):
        return "bar"

    def show_bar(self):
        return Foo.bar()

Expected usage:

foo = Foo()
assert foo.show_bar() == "bar"
# => throw exception: bar() missing 1 required positional argument: 'self'

Unittest tries unsuccessfully to catch this error with mock:

from unittest.mock import patch
with patch.object(Foo, 'bar', return_value="bar") as mock:
    foo = Foo()
    assert foo.show_bar() == "bar"
mock.assert_called_once()
# => no errors 

Ideally, I would like to assert that bar is called with self.bar() and NOT Foo.bar() ; which is wrong. Unfortunately, using mock.assert_called_with() does not take in account the self nor cls parameter, so I'm a bit confused.

EDIT: Trying to clarify. I'm looking for best practices to use the library unittest.mock when we need to patch an object's method. It seems not clear to me how to patch it, currently I have no way to assert if it's calling self.bar or Foo.bar.

Doomsday
  • 2,650
  • 25
  • 33
  • Don't mock anything, just call `f = Foo(); f.bar()` in your test code. With the broken implementation it will raise a `TypeError`, making the test fail. – bruno desthuilliers Sep 19 '17 at 11:22
  • Also and FWIW, this has nothing to do with `classmethod` - in your example `Foo.bar` is an unbound instance method. – bruno desthuilliers Sep 19 '17 at 11:23
  • Sorry, the code is oversimplified and I *need* to use mock because the actual code of the method is not callable without mocking its return value. Also, when `show_bar` developer calls `Foo.bar()`, I was assuming `Foo.bar` is either a classmethod or a staticmethod. It seems a reasonable statement? – Doomsday Sep 19 '17 at 12:34
  • Sorry, not enough context to make an informed judgement here... wrt/ the class or static method part I just wanted to make clear that `Foo.bar` was neither in you current implementation and that looking it up on the class would not make it one either. Possibly something got lost in translation ;) – bruno desthuilliers Sep 19 '17 at 13:15

1 Answers1

1

I don't really understand why you'd need to mock the method to test that it does not raise a TypeError when called but anyway... Someone else might explain how to solve this using unittest.mock, in the meantime you can just skip unittest.mock and mock Foo.bar by yourself:

 callargs = dict()  
 def mock_bar(self):
     callargs["self"] = self
     return "bar"

 foobar = Foo.__dict__["bar"] 
 Foo.bar = mock_bar
 try: 
     foo = Foo()
     assert foo.show_bar() == "bar"
     assert "self" in callargs
     assert callargs["self"] is foo
 finally:
     Foo.bar = foobar
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Actually I'm not trying to fix my issue which is trivial, neither mocking "manually" my class, I'm only looking for using `unittest.mock` correctly to replace methods calls in unittesting context. Thanks for trying to help though! – Doomsday Sep 19 '17 at 16:34