I have some utils function at src/utils/helper.py
Imagine I have a function called func_a
in utils/helper.py and it is used at multiple places in my project.
And every time I use it, I import it like this
from src.utils.helper import func_a
Now I want to mock this func_a
in my tests.
I want to create a fixture in conftest.py so that I don't need to write a mock function again and again for each test file.
The problem is, in my mock function I CANNOT write like this.
https://pypi.org/project/pytest-mock/
mocker.patch('src.utils.helper.func_a', return_value="some_value", autospec=True)
I have to write it like this for each test file
mocker.patch('src.pipeline.node_1.func_a', return_value="some_value", autospec=True)
As per the docs https://docs.python.org/3/library/unittest.mock.html#where-to-patch
Since I am importing func_a
like from src.utils.helper import func_a
I have to mock where it is used and not where it is defined.
But the problem with this approach is that I can not define it in my fixture in conftest.py
Directory Structure
├── src
│ ├── pipeline
│ │ ├── __init__.py
│ │ ├── node_1.py
│ │ ├── node_2.py
│ │ └── node_3.py
│ └── utils
│ ├── __init__.py
│ └── helper.py
└── tests
├── __init__.py
├── conftest.py
└── pipeline
├── __init__.py
├── test_node_1.py
├── test_node_2.py
└── test_node_3.py
Well, as you wrote, you have to use that patching if you use from xxx import
. Your first option is of course to use full module import in the production code instead:
node_1.py
import src.utils.helper
def node_1():
src.utils.helper.func_a()
I`m sure that you are aware of this, but I wanted to mention it anyway.
If you don't want to change the production code, you have to do the patching depending on the patched module, as you wrote. That basically means that you have to construct the patch location dynamically. Provided you have a symmetric naming of the tested functions and the test functions, you could do something like this:
conftest.py
@pytest.fixture
def mock_func_a(mocker, request):
node_name = request.node.name[5:] # we assume that the test function name is "test_node_1" for testing "node_1"
module_path = f'src.pipeline.{node_name}.func_a'
mocked = mocker.patch(module_path,
return_value="some_value",
autospec=True)
yield mocked
If you can't derive the patch path from the test itself, you have to add more information to the test function.
That probably makes only sense if you want to do more than just a patch in the fixture - otherwise you could also just add a patch
decorator directly.
You could add a custom mark that has the module path, or a part of the module path as an argument:
test_node_1.py
@pytest.mark.node("node_1")
def test_node(mock_func_a):
node_1()
mock_func_a.assert_called_once()
conftest.py
@pytest.fixture
def mock_func_a(mocker, request):
mark = next((m for m in request.node.iter_markers()
if m.name == 'node'), None) # find your custom mark
if mark is not None:
node_name = mark.args[0]
module_path = f'src.pipeline.{node_name}.func_a'
mocked = mocker.patch(module_path,
return_value="some_value",
autospec=True)
yield mocked
Or, if you need to provide the full path:
test_node_1.py
@pytest.mark.path("src.pipeline.node_1")
def test_node(mock_func_a):
...
conftest.py
@pytest.fixture
def mock_func_a(mocker, request):
mark = next((m for m in request.node.iter_markers()
if m.name == 'path'), None) # find your custom mark
if mark is not None:
node_name = mark.args[0]
module_path = f'{node_name}.func_a'
...