pytest-tricks
pytest-tricks copied to clipboard
Use @patch and @pytest.mark.parametrize at the same time
Hi,
great project! 👍 Thanks for all the efforts!
I would suggest to add another tip to your great site: how to correctly use patch
and pytest.mark.parametrize
at the same time. Here is a small, stupid test snippet:
import os.path
from unittest.mock import patch
def exists(path):
return os.path.exists(path)
@patch('os.path.exists')
@pytest.mark.parametrize('path, expect', [('/foo', True), ('/bar', False)])
def test_exists(mock_exists, path, expect):
def side_effect(args):
return expect
mock_exists.side_effect = side_effect
assert exists(path) == expect
This order would also work:
@pytest.mark.parametrize('path, expect', [('/foo', True), ('/bar', False)])
@patch('os.path.exists')
def test_exists(mock_exists, path, expect):
However, other orders or combinations won't.
Could be useful for others too, because I always have to try it out and never get it right in the first place. 😆
Hi @tomschr! 👋
Thank you for the kind words and your suggestion. Much appreciated! 😊
I use pytest-mock in my projects, when I need to mock things. Have you tried that before?
# -*- coding: utf-8 -*-
import os
import pytest
def exists(path):
return os.path.exists(path)
@pytest.fixture(autouse=True)
def mock_exists(mocker, expect):
mocker.patch('os.path.exists', return_value=expect)
@pytest.mark.parametrize(
"path, expect",
[('foo', True), ('bar', False)],
)
def test_exists(path, expect):
assert exists(path) == expect
/cc @nicoddemus ⬆️
I use pytest-mock in my projects, when I need to mock things. Have you tried that before?
I've heard about it before and I think, I've tried it also in the past - although I hardly remember. 😄
Thanks for the snippet, very much appreciated!
However, this introduce another dependency which I would like to avoid. The unittest.mock
module is available in the standard library. Another library (although probably very great) introduce another "moving target".
Maybe your suggestion would be something for another great article! 😊 One which use the standard module and another one with pytest-mock. 😊
@hackebrot Any idea how I can do this only for one test? autouse=True makes this global
The suggestion above for using a fixture works if you're injecting a dependency through constructor or method call.
If you're wanting to patch something low level like for example yourlib.api.request
(requests dependency), then it makes a little more sense to me to include
@pytest.mark.integration
@pytest.mark.parametrize(
('param1', 'param2',),
[
]
)
@mock.patch('yourlib.api.request')
def test_something(
[self],
mock_response,
fixture1, fixture2, fixture3,
param1, param2,
):
mock_response.return_value = MockJsonResponse([], 200)
response = yourlib.api.do_something_using_request()
assert response.status_code == 200
assert len(response.json()) == 0
Warning: this is pseudo-code
@noorul use mark.usefixtures
marker. Above example, revisited:
@pytest.fixture
def mock_exists(mocker, expect):
mocker.patch('os.path.exists', return_value=expect)
@pytest.mark.usefixtures('mock_exists')
@pytest.mark.parametrize(
"path, expect",
[('foo', True), ('bar', False)],
)
def test_exists(path, expect):
assert exists(path) == expect
def test_without_mocking():
assert os.path.exists != exists
@hoefling What is the standalone exists
in the above snippet?
os.path.exists
@nicoddemus I am not sure, I added the below imports at the beginning:
import os.path
from os.path import exists
import pytest
and got these failures:
___________________________________________________________________________________________________________________ test_exists[foo-True] ____________________________________________________________________________________________________________________
path = 'foo', expect = True
@pytest.mark.usefixtures('mock_exists')
@pytest.mark.parametrize(
"path, expect",
[('foo', True), ('bar', False)],
)
def test_exists(path, expect):
> assert exists(path) == expect
E AssertionError: assert False == True
E + where False = exists('foo')
/tmp/t.py:16: AssertionError
____________________________________________________________________________________________________________________ test_without_mocking ____________________________________________________________________________________________________________________
def test_without_mocking():
> assert os.path.exists != exists
E AssertionError: assert <function exists at 0x10a5a7b80> != exists
E + where <function exists at 0x10a5a7b80> = <module 'posixpath' from '/usr/local/Cellar/[email protected]/3.9.1_3/Frameworks/Python.framework/Versions/3.9/lib/python3.9/posixpath.py'>.exists
E + where <module 'posixpath' from '/usr/local/Cellar/[email protected]/3.9.1_3/Frameworks/Python.framework/Versions/3.9/lib/python3.9/posixpath.py'> = os.path
/tmp/t.py:20: AssertionError
It is, but the example is incomplete, I think it should be:
import pytest
import os.path
@pytest.fixture
def mock_exists(mocker, expect):
mocker.patch('os.path.exists', return_value=expect)
@pytest.mark.usefixtures('mock_exists')
@pytest.mark.parametrize(
"path, expect",
[('foo', True), ('bar', False)],
)
def test_exists(path, expect):
assert os.path.exists(path) == expect
@nicoddemus Yes, that is exactly what I did for that test to get it to pass, but I couldn't make out what change goes into test_without_mocking
.
Not sure, we will need to ask @hoefling
It's also possible to use patch as a context manager:
with patch('os.path.exists'):
... code ...
@haridsv a bit late to the party, but the test_without_mocking
is just there to demonstrate that no mocking is done when the fixture is not requested via usefixtures
marker. exists
is defined by the OP via
def exists(path):
return os.path.exists(path)
in the original post.