13

I feel like this may be relatively simple, but I'm pulling my hair out to get this working. I'd like to mock an entire class, and then specify the return value for one of this class's methods.

I already looked here, at several other questions, and of course in the docs. I'm still unable to get this to work. Please see my simple example below.

Contents of directory tmp:

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

Contents of my_module.py:

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

    def my_method(self):
        # For sake of simple example, always return 1.
        return 1


def create_class_call_method():
    """Create MyClass instance and call its my_method method, returning
    the result."""
    instance = MyClass()
    value = instance.my_method()
    return value

Contents of test_my_module.py:

import unittest
from unittest.mock import patch, Mock

from tmp import my_module


class MyClassTestCase(unittest.TestCase):

    def test_create_class_call_method(self):
        # Attempt to patch MyClass as well as specify a return_value for
        # the my_method method (spoiler: this doesn't work)
        with patch('tmp.my_module.MyClass',
                   my_method=Mock(return_value=2)):
            value = my_module.create_class_call_method()

        self.assertEqual(value, 2)


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

Results of running test_my_module.py:

2 != <MagicMock name='MyClass().my_method()' id='140234477124048'>

Expected :<MagicMock name='MyClass().my_method()' id='140234477124048'>
Actual   :2

Some other things I've tried:

  • Rather than ..., my_method=Mock(return_value=2)) in the patch statement, unpack a dictionary like so: **{'my_method.return_value': 2}
  • Nested with patch statements. Outer statement is simple like with patch('tmp.my_module.MyClass'):, inner statement attempts to patch my_method like so: with patch('tmp.my_module.MyClass.my_method, return_value=2)
  • Use patch decorators instead of context managers
  • Change patch statement to with patch('tmp.my_module.MyClass') as p: and then inside the with statement, try to set p like so: p.evaluate = Mock(return_value=2)

Any help is appreciated, thank you.

2
  • 1
    Not your issue but you may want to make a classmethod via the @classmethod decorator and keep it inside the class. (Though it will act a little different than what you implemented). Well worded question. Commented Jul 15, 2019 at 17:30
  • 1
    @Error-SyntacticalRemorse - thanks for your comment. In my real implementation, the create_class_call_method is slightly more complicated, and is actually used by a different class, which creates an object and calls one of its methods. So I don't think I can make it a classmethod Commented Jul 15, 2019 at 17:53

3 Answers 3

12

I've found a much better solution. In short, we need to mock out the return_value of the MyClass mock. Here's the working test code:

import unittest
from unittest.mock import patch, Mock, MagicMock

from tmp import my_module


class MyClassTestCase(unittest.TestCase):

    def test_create_class_call_method(self):
        # Create a mock to return for MyClass.
        m = MagicMock()
        # Patch my_method's return value.
        m.my_method = Mock(return_value=2)

        # Patch MyClass. Here, we could use autospec=True for more
        # complex classes.
        with patch('tmp.my_module.MyClass', return_value=m) as p:
            value = my_module.create_class_call_method()

        # Method should be called once.
        p.assert_called_once()
        # In the original my_method, we would get a return value of 1.
        # However, if we successfully patched it, we'll get a return
        # value of 2.
        self.assertEqual(value, 2)


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

And the successful results:

Ran 1 test in 0.002s

OK
Sign up to request clarification or add additional context in comments.

1 Comment

What made you use MagicMock for the class and Mock for the method?
6

I am not sure about the implementation of create_class_call_method, but try the following:

from unittest import mock

class MyClassTestCase(unittest.TestCase):
    @mock.patch('tmp.my_module.MyClass.my_method')
    @mock.patch('tmp.my_module.MyClass.__init__')
    def test_create_class_call_method(self, my_class_init, my_method_mock):
        my_class_init.return_value = None
        my_method.return_value = 2

        value = my_module.create_class_call_method()

        self.assertEqual(value, 2)

8 Comments

thanks for your answer. Will this patch all of MyClass, or just MyClass.my_method (I think the latter)? For my use case, I need MyClass to be mocked in its entirety (I don't want the code in its constructor to be executed), and I also need to specify the return value for a call to MyClass.my_method
@blthayer, it will patch this specific method. I consider you should follow this approach because the purpose of unit-testing is to test a unit, so if you mock a whole class, you are probably testing more than a unit.
I agree with your sentiment, and I'm certainly testing more than a "unit." However, I'm testing a method which in reality calls several methods (including creating an object and calling a method, like what I have in create_class_call_method) and aggregates the results. While I have more true "unit" tests for each of these methods, I want to cement in stone (or in a unit test :) ) that this method which aggregates results is calling particular methods and correctly performing the aggregation. Hence my desire to mock the whole class, and just get a return from a method.
@blthayer, if that is the case, I strongly suggest you to use several mocks, and probably declare them as annotations from the method not using the with statement to avoid too much nesting. Then use the features of mocks such as return_value or side_effect to reach the state you need before calling the actual testing unit.
thanks for the suggestion. Would you be willing to help me with a small example of what you're describing? I did try to take a similar approach to what you're describing at first, but came up short. The thing that's tripping me up is that a MyClass object is instantiated, and then has a method called in the function which I'm testing. So I'm not exactly sure how I would inject a mock in there without using patch.
|
6

I think the correct approach is found in this answer

note: the below is a sketch - may not get all the details of the OP correct

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

class MyClassTestCase(unittest.TestCase):

    @patch('tmp.my_module.MyClass')
    def test_create_class_call_method(self, my_class_mock):
       my_class_mock.return_value.my_method.return_value = 2
       value = my_module.create_class_call_method()
       self.assertEqual(value, 2)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.