简体   繁体   中英

Python unittest mock configuration not proliferating to test method

I have a complex class which I would like to mock in testing. However, I want to be able to set a particular attribute on my mocked class so that my function under test will work. Simple, runnable example with actual and expected output is below.

In my example, why doesn't my mock configuration proliferate to my_module.instantiate_class_do_stuff ? It's clear that MyClass is indeed being mocked, but my attempt to configure the mocking of MyClass.a just isn't sticking.

Contents of directory tmp :

tmp
├── __init__.py
├── my_module.py
└── test_my_module.py

Note that __init__.py is empty.

Contents of file my_module.py :

class MyClass:
    def __init__(self):
        # Do expensive operations that will be mocked in testing.
        self.a = 7


def instantiate_class_do_stuff():
    """Create MyClass instance and do stuff with it"""
    instance = MyClass()
    print('Value of a in instantiate_class_call_method: {}'.format(instance.a))
    # Do stuff with instance...

Contents of file test_my_module.py :

import unittest
from unittest.mock import patch
from tmp import my_module


class MyTestCase(unittest.TestCase):
    def setUp(self):
        print('*' * 79)

    def test_create_class_call_method_1(self):
        """Try using the Mock's configure_mock method."""

        with patch('tmp.my_module.MyClass') as p:
            p.configure_mock(a=10)
            print('Value of a in test_create_class_call_method_1: {}'
                  .format(p.a))
            my_module.instantiate_class_do_stuff()

        self.assertTrue(True)

    def test_create_class_call_method_2(self):
        """Try passing in kwargs to patch, which should make it to
        configure_mock, according to the docs.
        """

        with patch('tmp.my_module.MyClass', a=10) as p:
            print('Value of a in test_create_class_call_method_2: {}'
                  .format(p.a))
            my_module.instantiate_class_do_stuff()

        self.assertTrue(True)

    def test_create_class_call_method_alternate_2(self):
        """Try using patch.object instead of plain patch."""

        with patch.object(my_module, 'MyClass', a=10) as p:
            print('Value of a in test_create_class_call_method_3: {}'
                  .format(p.a))
            my_module.instantiate_class_do_stuff()

        self.assertTrue(True)


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

Actual output from running test_my_module.py :

*******************************************************************************
Value of a in test_create_class_call_method_1: 10
Value of a in instantiate_class_call_method: <MagicMock name='MyClass().a' id='140029598201104'>
*******************************************************************************
Value of a in test_create_class_call_method_2: 10
Value of a in instantiate_class_call_method: <MagicMock name='MyClass().a' id='140029598270096'>
*******************************************************************************
Value of a in test_create_class_call_method_3: 10
Value of a in instantiate_class_call_method: <MagicMock name='MyClass().a' id='140029598347088'>

Expected output from running test_my_module.py :

*******************************************************************************
Value of a in test_create_class_call_method_1: 10
Value of a in instantiate_class_call_method: 10
*******************************************************************************
Value of a in test_create_class_call_method_2: 10
Value of a in instantiate_class_call_method: 10
*******************************************************************************
Value of a in test_create_class_call_method_3: 10
Value of a in instantiate_class_call_method: 10

So, how can I make my attribute configuration be effective when my function under test is actually run?

Similar (but different) question I posted but didn't get a particularly satisfactory answer

Well, this certainly wasn't very intuitive to me, but I figured it out.

I noticed that in my testing methods, the value of p (the mocked instance of MyClass ) has a __repr__ like <MagicMock name='MyClass' id='140054079807440'> .

However, inside the function under test, instantiate_class_do_stuff , the value of instance has a __repr__ like <MagicMock name='MyClass()' id='140054079941392'> . The difference is in the () after MyClass .

So, it would appear that I wasn't mocking the correct thing - I want to mock the a attribute on the return value of MyClass .

So, here's what a working test looks like:

def test_creat_class_call_method(self):
    # Create a mock which will be returned by MyClass.
    m = MagicMock()
    m.a = 10

    with patch('tmp.my_module.MyClass', return_value=m) as p:
        my_module.instantiate_class_do_stuff()

And the print statement in my_module.instantiate_class_do_stuff prints the following:

Value of a in instantiate_class_do_stuff: 10

Success!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM