I hope this is a straightforward answer, but I've been stuck trying to sort this out. I'm looking to mock out the following scenario. I previously had something like this that worked:
# path/to/some/module.py
class MyClass:
# None of the methods or init should ever run in a test
def __init__():
pass
def do_thing():
pass
def get_my_class():
return MyClass()
# path/to/some/other/module.py
from path.to.some.module import get_my_class()
def do_something()
get_my_class().do_thing()
return 'something'
# test/path/to/some/other/module.py
from path.to.some.other.module import
def test_do_something():
with patch("path.to.some.module.MyClass") as mock:
assert do_something() = 'something'
mock().do_thing.assert_called_once()
So that was fine - the asserts worked, and the init and class methods were not called. But then I needed to move around some logic, and now I am unable to sort out how to get this to work from a mocking standpoint. The code itself works, I am just unable to get my mocks in a row. See below for the latest structure:
# path/to/some/module.py
class MyClass:
# None of the methods or init should ever run in a test
def __init__():
pass
def do_thing():
pass
class MyClassSingleton:
_my_class = None
def get_my_class():
if MyClassSingleton._my_class is None:
MyClassSingleton._my_class = MyClass()
return MyClassSingleton._my_class
# path/to/some/other/module.py
from path.to.some.module import get_my_class()
def do_something()
get_my_class().do_thing()
return 'something'
I tried updating my test to be something like:
with patch("path.to.some.module.MyClassSingleton._my_class") as mock:
but this leads to the MyClass.__init__
code running, which is no bueno.
My goal is for everything to work more or less as before from a mocking standpoint, ideally with as simple a setup/boilerplate as possible since I need to apply these changes to hundreds of tests. Any help would be appreciated. Thanks!
It seems like the easiest answer was to create a patch for the singleton, but also mock out the _client
reference as well. So I made a fixture like this:
@pytest.fixture
def my_client() -> MagicMock:
"""
Provide a mocked client via singleton that does not initialize
"""
mock_provider = MagicMock()
with patch("path.to.some.module.MyClassSingleton", mock_provider):
yield mock_provider._client
def test_do_something(my_client):
assert do_something() = 'something'
mock.do_thing.assert_called_once()
It is possible I could have mocked out get_my_class
, but as I was moving towards a fixture-based approach, this proved to be less desirable, as I would import get_my_class
into various modules, which meant the patch target reference to get_my_class
would have to change, which would mean parameterizing fixtures which I didn't want to chase down.
Because the provider/singleton class was only accessed via get_my_class
, by mocking the provider I was able to have a single fixture that would work everywhere.