Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to patch globally in pytest?

Tags:

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?

like image 244
Ranjith Ramachandra Avatar asked Aug 04 '16 11:08

Ranjith Ramachandra


People also ask

What is Monkeypatch in pytest?

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.

What is Conftest py in pytest?

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.

What is Autouse in pytest?

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.


2 Answers

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.

like image 107
Ranjith Ramachandra Avatar answered Sep 19 '22 13:09

Ranjith Ramachandra


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
like image 44
Intrastellar Explorer Avatar answered Sep 22 '22 13:09

Intrastellar Explorer