Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python unittest: to mock.patch() or just replace method with Mock?

When mocking classes or methods when writing unittests in Python, why do I need to use @patch decorator? I just could replace the method with Mock object without any patch annotation.

Examples:

class TestFoobar(unittest.TestCase):
def setUp(self):
    self.foobar = FooBar()

# 1) With patch decorator:

@patch.object(FooBar, "_get_bar")
@patch.object(FooBar, "_get_foo")
def test_get_foobar_with_patch(self, mock_get_foo, mock_get_bar):
    mock_get_bar.return_value = "bar1"
    mock_get_foo.return_value = "foo1"

    actual = self.foobar.get_foobar()

    self.assertEqual("foo1bar1", actual)

# 2) Just replacing the real methods with Mock with proper return_value:

def test_get_foobar_with_replacement(self):
    self.foobar._get_foo = Mock(return_value="foo2")
    self.foobar._get_bar = Mock(return_value="bar2")

    actual = self.foobar.get_foobar()

    self.assertEqual("foo2bar2", actual)

Could someone produce an example, where patch decorator is good and replacing is bad?

We have always used patch decorator with our team, but after reading this comment for a post, I got the idea that maybe we could write nicer-looking code without the need of patch decorators.

I understand that patching is temporary, so maybe with some cases, it is dangerous to not use patch decorator and replace methods with mock instead? Could it be that replacing objects in one test method can affect the result of the next test method?

I tried to prove this, but came up empty: both tests pass in the next code:

def test_get_foobar_with_replacement(self):
    self.foobar._get_foo = Mock(return_value="foo2")
    self.foobar._get_bar = Mock(return_value="bar2")

    actual = self.foobar.get_foobar()

    self.assertIsInstance(self.foobar._get_bar, Mock)
    self.assertIsInstance(self.foobar._get_foo, Mock)
    self.assertEqual("foo2bar2", actual)

def test_get_foobar_with_real_methods(self):

    actual = self.foobar.get_foobar()

    self.assertNotIsInstance(self.foobar._get_bar, Mock)
    self.assertNotIsInstance(self.foobar._get_foo, Mock)
    self.assertIsInstance(self.foobar._get_bar, types.MethodType)
    self.assertIsInstance(self.foobar._get_foo, types.MethodType)
    self.assertEqual("foobar", actual)

Full source code (Python 3.3): dropbox.com/s/t8bewsdaalzrgke/test_foobar.py?dl=0

like image 723
kristjanroosild Avatar asked Sep 28 '14 16:09

kristjanroosild


1 Answers

patch.object will restore the item you patched to its original state after the test method returns. If you monkey-patch the object yourself, you need to restore the original value if that object will be used in another test.

In your two examples, you are actually patching two different things. Your call to patch.object patches the class FooBar, while your monkey patch patches a specific instance of FooBar.

Restoring the original object isn't important if the object will be created from scratch each time. (You don't show it, but I assume self.foobar is being created in a setUp method, so that even though you replace its _get_foo method, you aren't reusing that specific object in multiple tests.)

like image 50
chepner Avatar answered Sep 28 '22 22:09

chepner