Mocking a class method and changing some object attributes in Python

25,502

Apologies in advance if I don't understand what you are trying to do, but I think this might work:

import unittest
from unittest.mock import patch

class MyClass:

    def __init__(self):
        self.x = 0
        self.y = 0

    def some_method(self):   
        self.x = 4   
        self.y = 6    

class OtherClass:

    def other_method(self):
        self.x = 5
        self.y = 16

class MyTestClass(unittest.TestCase):

    @patch('__main__.MyClass.some_method', new=OtherClass.other_method)
    def test_patched(self):
        a = MyClass()
        a.some_method()
        self.assertEqual(a.x, 5)
        self.assertEqual(a.y, 16)

    def test_not_patched(self):
        a = MyClass()
        a.some_method()
        self.assertEqual(a.x, 4)
        self.assertEqual(a.y, 6)

if __name__ == "__main__":
    unittest.main()

This replaces some_method() with other_method() when patched, which sets different values for attributes x, y, and when the test is run, it gives the results:

..
----------------------------------------------------------------------
Ran 2 tests in 0.020s

OK

EDIT: to answer question about how to do inside the test function without mocking a class...

def test_inside_patch(self):
    def othermethod(self):
        self.x = 5
        self.y = 16
    patcher = patch('__main__.MyClass.some_method', new=othermethod)
    patcher.start()
    a = MyClass()
    a.some_method()
    self.assertEqual(a.x, 5)
    self.assertEqual(a.y, 16) 
    patcher.stop()

Make sure you call start() and stop() on the patcher otherwise you can get into a situation where the patch is active and you don't want it to be. Note that to define the mock function inside the test code function, I didn't use patch as a decorator, because the mock function has to be defined before using the 'new' keyword in patch. If you want to use patch as a decorator you have to define the mock function someplace before the patch, defining it inside of MyTestClass also works, but it seems you really want to have the mock function defined inside your test function code.

EDIT: added summary of 4 ways I see to do this...

# first way uses a class outside MyTest class
class OtherClass:
    def other_method(self):
        ...

class MyTest(unittest.TestCase):

    @patch('path_to_MyClass.some_method', new=OtherClass.other_method)
    def test_1(self)
        ...

    # 2nd way uses class defined inside test class    
    class MyOtherClass:
        def other_method(self):
            ...
    @patch('path_to_MyClass.some_method', new=MyOtherClass.other_method)    
    def test_2(self):
        ...

    # 3rd way uses function defined inside test class but before patch decorator 
    def another_method(self):
        ...
    @patch('path_to_MyClass.some_method', new=another_method)    
    def test_3(self):
        ...

    # 4th way uses function defined inside test function but without a decorator
    def test_4(self):
        def yet_another_method(self):
            ...
        patcher = patch('path_to_MyClass.some_method', new=yet_another_method)
        patcher.start()
        ...
        patcher.stop()

None of these uses a side_effect, but they all solve the problem of mocking a class method and changing some attributes. Which one you choose depends on the application.

Share:
25,502
Ahmed Ayadi
Author by

Ahmed Ayadi

Updated on August 28, 2020

Comments

  • Ahmed Ayadi
    Ahmed Ayadi over 3 years

    I am new to mock in Python. I want to know how to replace (mock) a class method while testing with another one, knowing that the original just changes some attributes of self without returning any value. For example:

    def some_method(self):   
        self.x = 4   
        self.y = 6   
    

    So here I can't just change the return_value of the mock. I tried to define a new function (that should replace the original) and give it as side_effect to the mock. But how can I make the mocking function change attributes of the object in the class. Here is my code:

    @patch('path.myClass.some_method')
    def test_this(self,someMethod):
    
        def replacer(self):
            self.x = 5
            self.y = 16
    
    some_method.side_effect = replacer
    

    So how does Python now understands the self argument of replacer? Is that the self of the test class, or the self as the object of the class under test?

  • Ahmed Ayadi
    Ahmed Ayadi over 8 years
    Thank you for your answer. It is very helpful. But is it neccessary to create a new class and then a new method in this new class ? Couldn'I just define the other_method within the testing code, and then give it as a side_effect ?
  • Steve Misuta
    Steve Misuta over 8 years
    @AhmedAyadi Yes, it is possible to define the mock function inside the test function code, but I'm not sure about using a side_effect. See my edit to my post. PS. if this answer solves your question, please accept it, thanks!
  • Ahmed Ayadi
    Ahmed Ayadi over 8 years
    Thank you very much :) That does answer my question