Reputation: 1766
I'm trying to mock an object that makes some expensive network calls when it's initialized, but the parent class instantiating it isn't recognizing the patch from my unit test.
Some similar questions I've already looked at:
--src
|-- resource.py
|-- family.py
|-- test_child.py
class Resource:
def __init__(self):
print("Expensive network call")
def use_resource(self) -> str:
print("Another expensive network call")
return "bar"
from resource import Resource
class Parent:
def __init__(self):
print("In the Parent constructor.")
self.resource = Resource()
def foo(self):
print("Parent's foo")
print(self.resource.use_resource())
class Child(Parent):
def __init__(self):
super().__init__()
print("In the Child constructor")
def foo(self):
print("Child's foo")
super().foo()
import unittest
from unittest.mock import patch
from family import Child
class TestChild(unittest.TestCase):
@patch('resource.Resource')
def test_foo(self, mock_resource):
mock_resource.return_value.use_resource.return_value = "mock_bar"
child = Child()
child.foo()
mock_resource.return_value.use_resource.assert_called_once()
unittest.main()
Since I'm patching resource.Resource
, I'm expecting to avoid the expensive network calls that normally occur when a Parent
instantiates a Resource
. Theoretically the output of running test_child.py
should look like this:
In the Parent constructor.
In the Child constructor
Child's foo
Parent's foo
mock_bar
However, despite patching resource.Resource
in the test, the Parent
is still instantiating an actual Resource
as evidenced by the presence of the "Expensive network call" messages in the output and the failed assert_called_once
assertion.
In the Parent constructor.
Expensive network call # Should have been patched
In the Child constructor
Child's foo
Parent's foo
Another expensive network call # Should have been patched
bar # Should have been patched
F
======================================================================
FAIL: test_foo (__main__.TestChild)
----------------------------------------------------------------------
Traceback (most recent call last):
File "[REMOVED]\unittest\mock.py", line 1342, in patched
return func(*newargs, **newkeywargs)
File "[REMOVED]\test_child.py", line 13, in test_foo
mock_resource.return_value.use_resource.assert_called_once()
File "[REMOVED]\unittest\mock.py", line 886, in assert_called_once
raise AssertionError(msg)
AssertionError: Expected 'use_resource' to have been called once. Called 0 times.
----------------------------------------------------------------------
Ran 1 test in 0.005s
Parent
's instantiation of a Resource
uses a mocked Resource
instead of an actual Resource
?Upvotes: 1
Views: 1606
Reputation: 1766
@patch(family.Resource)
instead of @patch(resource.Resource)
There are two key ideas that explain why your patch failed:
Parent
class instantiates a Resource
, it is not directly instantiating a resource.Resource
, rather it is instantiating the family
module's import of a Resource
.family
module does import resource.Resource
(which you are indeed patching), that import occurs prior to your patch because you import the family
module prior to executing the patch.See the unittest.mock documentation if you want more details. This article from Medium is also really helpful for understanding the oddities of mocking in Python.
For completeness, here's your test_child.py
with the correction...
import unittest
from unittest.mock import patch
from family import Child
class TestChild(unittest.TestCase):
@patch('family.Resource') # Changed this line
def test_foo(self, mock_resource):
mock_resource.return_value.use_resource.return_value = "mock_bar"
# Alternatively, move `from family import Child` here.
child = Child()
child.foo()
mock_resource.return_value.use_resource.assert_called_once()
unittest.main()
... and the test output matches the expected output in your question.
In the Parent constructor.
In the Child constructor
Child's foo
Parent's foo
mock_bar
.
----------------------------------------------------------------------
Ran 1 test in 0.004s
OK
Upvotes: 1