I use pytest quite a bit for my code. Sample code structure looks like this. The entire codebase is python-2.7
core/__init__.py
core/utils.py
#feature
core/feature/__init__.py
core/feature/service.py
#tests
core/feature/tests/__init__.py
core/feature/tests/test1.py
core/feature/tests/test2.py
core/feature/tests/test3.py
core/feature/tests/test4.py
core/feature/tests/test10.py
The service.py
looks something like this:
from modules import stuff
from core.utils import Utility
class FeatureManager:
# lots of other methods
def execute(self, *args, **kwargs):
self._execute_step1(*args, **kwargs)
# some more code
self._execute_step2(*args, **kwargs)
utility = Utility()
utility.doThings(args[0], kwargs['variable'])
All the tests in feature/tests/*
end up using core.feature.service.FeatureManager.execute
function. However utility.doThings()
is not necessary for me to be run while I am running tests. I need it to happen while the production application runs but I do not want it to happen while the tests are being run.
I can do something like this in my core/feature/tests/test1.py
from mock import patch
class Test1:
def test_1():
with patch('core.feature.service.Utility') as MockedUtils:
exectute_test_case_1()
This would work. However I added Utility
just now to the code base and I have more than 300 test cases. I would not want to go into each test case and write this with
statement.
I could write a conftest.py
which sets a os level environment variable based on which the core.feature.service.FeatureManager.execute
could decide to not execute the utility.doThings
but I do not know if that is a clean solution to this issue.
I would appreciate if someone could help me with global patches to the entire session. I would like to do what I did with the with
block above globally for the entire session. Any articles in this matter would be great too.
TLDR: How do I create session wide patches while running pytests?
monkeypatch can be used to patch functions dependent on the user to always return a specific value. In this example, monkeypatch. setattr is used to patch Path. home so that the known testing path Path("/abc") is always used when the test is run. This removes any dependency on the running user for testing purposes.
conftest.py is where you setup test configurations and store the testcases that are used by test functions. The configurations and the testcases are called fixture in pytest. The test_*. py files are where the actual test functions reside.
pytest is a very robust framework that comes with lots of features. One such feature is the autouse fixtures, a.k.a xUnit setup on steroids. They are a special type of fixture that gets invoked automatically, and its main use case is to act as a setup/teardown function.
I added a file called core/feature/conftest.py
that looks like this
import logging
import pytest
@pytest.fixture(scope="session", autouse=True)
def default_session_fixture(request):
"""
:type request: _pytest.python.SubRequest
:return:
"""
log.info("Patching core.feature.service")
patched = mock.patch('core.feature.service.Utility')
patched.__enter__()
def unpatch():
patched.__exit__()
log.info("Patching complete. Unpatching")
request.addfinalizer(unpatch)
This is nothing complicated. It is like doing
with mock.patch('core.feature.service.Utility') as patched:
do_things()
but only in a session-wide manner.
Building on the currently accepted answer for a similar use case (4.5 years later), using unittest.mock's patch
with a yield
also worked:
from typing import Iterator
from unittest.mock import patch
import pytest
@pytest.fixture(scope="session", autouse=True)
def default_session_fixture() -> Iterator[None]:
log.info("Patching core.feature.service")
with patch("core.feature.service.Utility"):
yield
log.info("Patching complete. Unpatching")
Aside
I didn't use autouse=True
, I used @pytest.mark.usefixtures("default_session_fixture")
to integrate this into my unit tests on a test-by-test basis.
Versions
Python==3.8.6
pytest==6.2.2
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