I would like to mock a module-level function used to initialize a class-level (not instance) attribute. Here's a simplified example:
# a.py
def fn():
return 'asdf'
class C:
cls_var = fn()
Here's a unittest attempting to mock a.fn():
# test_a.py
import unittest, mock
import a
class TestStuff(unittest.TestCase):
# we want to mock a.fn so that the class variable
# C.cls_var gets assigned the output of our mock
@mock.patch('a.fn', return_value='1234')
def test_mock_fn(self, mocked_fn):
print mocked_fn(), " -- as expected, prints '1234'"
self.assertEqual('1234', a.C.cls_var) # fails! C.cls_var is 'asdf'
I believe the problem is where to patch but I've tried both variations on import with no luck. I've even tried moving the import statement into test_mock_fn() so that the mocked a.fn() would "exist" before a.C comes into scope - nope, still fails.
Any insight would be greatly appreciated!
side_effect: side_effect allows to perform side effects, including raising an exception when a mock is called. patch(): The patch() decorator/ context manager makes it easy to mock classes or objects in a module under test.
unittest.mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they have been used. unittest.mock provides a core Mock class removing the need to create a host of stubs throughout your test suite.
To mock an entire class you'll need to set the return_value to be a new instance of the class. @mock.
What is actually happening here is that when you actually import your module, fn()
would have already executed. So, the mock comes in after you have already evaluated the method that is being stored in your class attribute.
So, by the time you try to mock the method it is too late for the testing you are trying to do.
You can even see this happening if you simply add a print statement in your method:
def fn():
print("I have run")
return "asdf"
In your test module, when you import a
and simply run without even running your test and you will see I have run
will come up in your console output without running anything explicitly from your a
module.
So, there are two approaches you can take here. Either you can use the PropertyMock
to mock out the class attribute to what you are expecting it to store, like this:
@mock.patch('a.C.cls_var', new_callable=PropertyMock)
def test_mock_fn(self, mocked_p):
mocked_p.return_value = '1234'
self.assertEqual('1234', a.C.cls_var)
Now, you have to be also aware, that by doing this, you are still actually running fn
, but with this mocking, you are now holding '1234' in cls_var
with the PropertyMock
you have set up.
The following suggestion (probably less ideal since it requires a design change) would be requiring revising why you are using a class attribute. Because if you actually set that class attribute as an instance attribute, that way when you create an instance of C
then your method will execute, which at that point it will use your mock.
So, your class looks like:
class C:
def __init__(self):
self.var = fn()
and your test would look like:
@mock.patch('a.fn', return_value='1234')
def test_mock_fn(self, mocked_p):
self.assertEqual('1234', a.C().var)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With