Search code examples
pythonpython-unittestpatchpython-unittest.mock

Patching child TestCase class affects parent TestCase class


I need to test a function which uses feature toggles to turn some functionality on and off. Say, a function like this:

def func_to_test(hello_value: str):
    if toggle_is_active('only_hello_and_hi'):
        if hello_value not in ('hello', 'hi'):
            return
    print(hello_value)

And now I want to test this function for both feature toggle states. For the off toggle I would do something like this:

class InactiveToggleTestCase(unittest.TestCase):
    hello_values = ['hello', 'hi', 'bonjour', 'sup']

    def test_func(self):
        for hello_value in self.hello_values:
            with self.subTest(hello_value=hello_value):
                func_to_test(hello_value)
        # assert print was called with expected values

And for the active state I want to just inherit the class and patch the toggle state:

@patch('toggle_is_active', lambda x: True)
class ActiveToggleTestCase(InactiveToggleTestCase):
    hello_values = ['hello', 'hi']

This way I don't need to rewrite the test itself. The thing is, the parent class also gets the patch from the child class and test case for inactive toggle state doesn't pass anymore.

How can I avoid this effect? I could just duplicate the test, of course, but this doesn't seem to be right and also if there is a plenty of tests the inheritance would be really helpful.


Solution

  • The thing is, the parent class also gets the patch from the child class and test case for inactive toggle state doesn't pass anymore.

    Firstly, I can't reproduce this problem. If I make two files:

    t.py

    def toggle_is_active(_):
        return False
    

    mre.py

    import unittest
    from unittest.mock import patch
    import t
    
    class InactiveToggleTestCase(unittest.TestCase):
    
        def test_func(self):
            print(t.toggle_is_active)
            
    
    @patch('t.toggle_is_active', lambda x: True)
    class ActiveToggleTestCase(InactiveToggleTestCase):
        pass
    
    unittest.main()
    

    Then running python mre.py prints

    python mre.py
    <function <lambda> at 0x109f5b040>
    .<function toggle_is_active at 0x10a1b5430>
    

    This shows that the function is being patched for one class and not the other. Could you give the exact steps to reproduce (e.g. the way you call unittest and the file/folder structure you are using)?

    IMO, the real issue is that this is not a good use of inheritance. ActiveToggleTestCase shouldn't really be a child of Inactive and it's not worth shoehorning it into that role just to reduce duplication. I think what you're trying to express is that there is some concept of a ToggleCaseTest and there are two variants of it so you could do

    class ToggleMixin():
        def test_func(self):
            print(t.toggle_is_active)
            print(self.hello_values)
            # your previous testing code here...
    
    class InactiveToggleTestCase(unittest.TestCase, ToggleMixin):
        hello_values = ['hello', 'hi', 'bonjour', 'sup']
    
    
    @patch('t.toggle_is_active', lambda x: True)
    class ActiveToggleTestCase(unittest.TestCase, ToggleMixin):
        hello_values = ['hello', 'hi']
    

    If you can't get that to work, you could also do

    class ToggleTestCase(unittest.TestCase):
        def helper_func(self, hello_values):
            for hello_value in hello_values:
                with self.subTest(hello_value=hello_value):
                    func_to_test(hello_value)
            # assert print was called with expected values
    
        def test_inactive(self):
            hello_values = ['hello', 'hi', 'bonjour', 'sup']
            self.helper_func(hello_values)
    
        def test_active(self):
            hello_values = ['hello', 'hi']
            with patch("t.toggle_is_active") as mock_toggle:
                self.helper_func(hello_values)
    

    This should be safer since we are being explicit about when we patch toggle_is_active.