Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python mock - patching a method without obstructing implementation

Tags:

python

mocking

Is there a clean way to patch an object so that you get the assert_call* helpers in your test case, without actually removing the action?

For example, how can I modify the @patch line to get the following test passing:

from unittest import TestCase from mock import patch   class Potato(object):     def foo(self, n):         return self.bar(n)      def bar(self, n):         return n + 2   class PotatoTest(TestCase):      @patch.object(Potato, 'foo')     def test_something(self, mock):         spud = Potato()         forty_two = spud.foo(n=40)         mock.assert_called_once_with(n=40)         self.assertEqual(forty_two, 42) 

I could probably hack this together using side_effect, but I was hoping there would be a nicer way which works the same way on all of functions, classmethods, staticmethods, unbound methods, etc.

like image 551
wim Avatar asked Sep 01 '14 14:09

wim


People also ask

What is patch in Python mock?

patch() unittest. mock provides a powerful mechanism for mocking objects, called patch() , which looks up an object in a given module and replaces that object with a Mock . Usually, you use patch() as a decorator or a context manager to provide a scope in which you will mock the target object.

What is the difference between mock and MagicMock?

With Mock you can mock magic methods but you have to define them. MagicMock has "default implementations of most of the magic methods.". If you don't need to test any magic methods, Mock is adequate and doesn't bring a lot of extraneous things into your tests.

How do you use mock exception in Python?

Just assign the exception to side_effect instead: mockedObj. raiseError. side_effect = Exception("Test") . You don't have to no: and his edit has a third way of doing it where he is making a Mock with the side effect set, but its still a valid, and good thing to know how to do in testing.

What is mocking in Python testing?

What Is Mocking? A mock object substitutes and imitates a real object within a testing environment. It is a versatile and powerful tool for improving the quality of your tests. One reason to use Python mock objects is to control your code’s behavior during testing.

How to create a mock object in Python?

For a list of all the command-line options, use -h (or --help) option: The mock is a Python library to create mock objects, which helps us in replacing parts of your system under test with mock objects and set assertions about how they have been used.

How do you use patch in Python context manager?

To use patch () as a context manager, you use Python’s with statement: When the test exits the with statement, patch () replaces the mocked object with the original. Until now, you’ve mocked complete objects, but sometimes you’ll only want to mock a part of an object.

How to use patch() instead of using magicmock() object?

Instead of using the MagicMock () object directly, you can use the patch (). The following test module test_total_with_patch_decorator.py tests the total.py module using the patch () as a function decorator: How it works. Second, decorate the test_calculate_total () test method with the @patch decorator.


2 Answers

Similar solution with yours, but using wraps:

def test_something(self):     spud = Potato()     with patch.object(Potato, 'foo', wraps=spud.foo) as mock:         forty_two = spud.foo(n=40)         mock.assert_called_once_with(n=40)     self.assertEqual(forty_two, 42) 

According to the documentation:

wraps: Item for the mock object to wrap. If wraps is not None then calling the Mock will pass the call through to the wrapped object (returning the real result). Attribute access on the mock will return a Mock object that wraps the corresponding attribute of the wrapped object (so attempting to access an attribute that doesn’t exist will raise an AttributeError).


class Potato(object):      def spam(self, n):         return self.foo(n=n)      def foo(self, n):         return self.bar(n)      def bar(self, n):         return n + 2   class PotatoTest(TestCase):      def test_something(self):         spud = Potato()         with patch.object(Potato, 'foo', wraps=spud.foo) as mock:             forty_two = spud.spam(n=40)             mock.assert_called_once_with(n=40)         self.assertEqual(forty_two, 42) 
like image 180
falsetru Avatar answered Sep 20 '22 23:09

falsetru


This answer address the additional requirement mentioned in the bounty from user Quuxplusone:

The important thing for my use-case is that it work with @patch.mock, i.e. that it not require me to insert any code in between my constructing of the instance of Potato (spud in this example) and my calling of spud.foo. I need spud to be created with a mocked-out foo method from the get-go, because I do not control the place where spud is created.

The use case described above could be achieved without too much trouble by using a decorator:

import unittest import unittest.mock  # Python 3  def spy_decorator(method_to_decorate):     mock = unittest.mock.MagicMock()     def wrapper(self, *args, **kwargs):         mock(*args, **kwargs)         return method_to_decorate(self, *args, **kwargs)     wrapper.mock = mock     return wrapper  def spam(n=42):     spud = Potato()     return spud.foo(n=n)  class Potato(object):      def foo(self, n):         return self.bar(n)      def bar(self, n):         return n + 2  class PotatoTest(unittest.TestCase):      def test_something(self):         foo = spy_decorator(Potato.foo)         with unittest.mock.patch.object(Potato, 'foo', foo):             forty_two = spam(n=40)         foo.mock.assert_called_once_with(n=40)         self.assertEqual(forty_two, 42)   if __name__ == '__main__':     unittest.main() 

If the method replaced accepts mutable arguments which are modified under test, you might wish to initialize a CopyingMock* in place of the MagicMock inside the spy_decorator.

*It's a recipe taken from the docs which I've published on PyPI as copyingmock lib

like image 27
wim Avatar answered Sep 16 '22 23:09

wim