Let's say i have a method is_validate, which internally calls validate method from library gateway.service
import gateway.service
from gateway.service.exception import ValidatorException
def is_validate():
try:
gateway.service.validate() # which throws ValidatorException
return True
except ValidatorException ex:
return False
How to unit test is_validate method, mocking gateway.service.validate to throw ValidatorException ?
You can do this with a combination of:
mocking a function (creating a fake version of the function dictating what it returns);
monkeypatching the actual function with your mock version;
and using pytest to actually run the test.
I've written a description of how to do this (pulled from my own work) here, in case an example I know works is useful.
But this is what I think you'll need to do in your code:
Define a pytest fixture to mock the scenario you want to test, using monkeypatch to fake the results you want from the parts of the is_validate().
And a test to check that a ValidatorException is raised; the code that raises the error in the test is in the pytest fixture. The entire pytest fixture defined there is passed as a parameter to the test.
import pytest
from unittest import TestCase
import gateway.service
from gateway.service.exception import ValidatorException
# Create object for accessing unittest assertions
assertions = TestCase("__init__")
#pytest.fixture
def validation_fails(monkeypatch):
"""
Mocks a call to gateway.service.validate().
"""
def mock_validate(*args, **kwargs):
"""Mock an absolute file path."""
raise ValidatorException
# Replace calls to existing methods with the mocked versions
monkeypatch.setattr(gateway.service, "validate", mock_validate)
def test_validation_fails(validation_fails):
"""Test validation."""
# check that the correct exception is raised
with assertions.assertRaises(ValidatorException):
is_validate()
Note: This does not include whatever setup is required to get pytest working for your project.
-------------------------------------
mymodule.py
-------------------------------------
import os
def remove(file_path):
if os.path.exists(file_path):
os.remove(file_path)
else:
print('File does not exist')
-------------------------------------
from mymodule import remove
import mock
import unittest
class RemoveTestCase(unittest.TestCase):
#mock.patch('mymodule.os.path')
#mock.patch('mymodule.os.remove')
def test_remove(self, mock_os_remove, mock_os_path):
mock_os_path.exists.return_value = True
#side_effect
remove("any path")
mock_os_remove.assert_called_with("any path")
I was able to mock gateway.service.validate by referencing it with module name where is_validate method is present.
ex: #mock.patch('mymodule.gateway.service.validate')
Reference this doc for more info
Related
pytest : How to write pytest code to detect a "func" called without actually executing the "func" ,
target.py:
import requests
import threading
CFG=None
targets={}
def add_target(func):
targets[func.__name__] = func
return func
def parent_func(url):
for funct in set(targets):
threading.Thread(target=targets[funct], args=(CFG[url],)).start()
#add_target
def child_func(url):
try:
response = requests.request("POST", url,
headers={"Content-Type": "application/json"},
data="{\"text\": \"target py test\"}")
response.raise_for_status()
except Exception as err:
raise RuntimeError(err)
test_target.py:
During testing, I want child_func() to get called but the child_func() body should not get executed. Rather, after running tests, child_func() gets executed & test run results in "AssertionError: False is not true "
from unittest.mock import Mock, patch
import target
# Third-party imports...
from nose.tools import assert_true, assert_is_not_none
target.CFG={'url':"some valid url"}
#patch('target.child_func')
def test_child_func(mock_child_func):
parent_func("url")
assert_true(mock_child_func.called)
First (for the sake of my own sanity) let's whittle this down to an actual MRE -- we don't actually need threading or requests or any of the implementation details of child_func to demonstrate the problem here.
target.py:
targets={}
def add_target(func):
targets[func.__name__] = func
return func
def parent_func(url):
for func in targets.values():
func(url)
#add_target
def child_func(url):
raise NotImplementedError("The test shouldn't actually call this function!")
test_target.py:
from unittest.mock import Mock, patch
from target import parent_func
#patch('target.child_func')
def test_parent_func(mock_child_func):
parent_func("url")
assert mock_child_func.call_count == 1
Within our test, we want parent_func to call mock_child_func, not the real child_func. But when we run our test, we can quickly see that our patch didn't work:
============================================== short test summary info ===============================================
FAILED test_target.py::test_parent_func - NotImplementedError: The test shouldn't actually call this function!
The reason for this is that parent_func doesn't call child_func, it calls targets['child_func']. Patching 'target.child_func' doesn't modify the actual function, it modifies what the name child_func points to in the target module -- and that name isn't in use in the code under test!
The easiest fix here is to patch target.targets:
from unittest.mock import Mock, patch
from target import parent_func
#patch('target.targets', new_callable=dict)
def test_parent_func(mock_targets):
mock_targets['child_func'] = Mock()
parent_func("url")
assert mock_targets['child_func'].call_count == 1
Now our test passes because when parent_func iterates through its targets, it gets our mock_targets dict, which we can populate with whatever functions/mocks we want.
Does pytest provides functionality like unittest.mock to check if the mock was actually called once(or once with some parameter)?
Sample Source code:
my_package/my_module.py
from com.abc.validation import Validation
class MyModule:
def __init__(self):
pass
def will_call_other_package(self):
val = Validation()
val.do()
def run(self):
self.will_call_other_package()
Sample test code for the above source code:
test_my_module.py
import pytest
from pytest_mock import mocker
from my_package.my_module import MyModule
#pytest.fixture
def mock_will_call_other_package(mocker):
mocker.patch('my_package.my_module.will_call_other_package')
#pytest.mark.usefixtures("mock_will_call_other_package")
class TestMyModule:
def test_run(self):
MyModule().run()
#check `will_call_other_package` method is called.
#Looking for something similar to what unittest.mock provide
#mock_will_call_other_package.called_once
If you want to use a fixture that does the patching, you can move the patching into a fixture:
import pytest
from unittest import mock
from my_package.my_module import MyModule
#pytest.fixture
def mock_will_call_other_package():
with mock.patch('my_package.my_module.will_call_other_package') as mocked:
yield mocked
# the mocking will be reverted here, e.g. after the test
class TestMyModule:
def test_run(self, mock_will_call_other_package):
MyModule().run()
mock_will_call_other_package.assert_called_once()
Note that you have to use the fixture parameter in the test. Just using #pytest.mark.usefixtures will not give you access to the mock itself. You can still use it to be effective in all tests in the class, if you don't need to access the mock in all tests (or use autouse=True in the fixture).
Also note that you don't need pytest-mock here - but as mentioned by #hoefling, using it makes the fixture better readable, because you don't need the with clause :
#pytest.fixture
def mock_will_call_other_package(mocker):
yield mocker.patch('my_package.my_module.will_call_other_package')
As an aside: you don't need to import mocker. Fixtures are looked up by name, and available automatically if the respective plugin is installed.
You could try this:
import pytest
from my_package.my_module import MyModule
def test_run(mocker):
mocker.patch('my_package.my_module.will_call_other_package')
MyModule().run()
mock_will_call_other_package.assert_called_once()
First of all, you may not need the burden of an external library such as pytest_mock, because pytest already got you covered using the integration with unittest.
You also do not need to use the usefixtures because whenever you need a fixture, you just receive it in your test method.
An ideal scenario based on your own code would look similar to this:
import pytest
from unittest.mock import patch
from com.abc.validation import Validation
class MyModule:
def __init__(self):
pass
def will_call_other_package(self):
val = Validation()
val.do()
def run(self):
self.will_call_other_package()
#pytest.fixture
def call_other_module():
with patch("my_package.my_module.MyModule.will_call_other_package") as _patched:
yield _patched
class TestMyModule:
def test_run_will_call_other_package(self, call_other_module):
call_other_module.assert_not_called()
obj = MyModule()
call_other_module.assert_not_called()
obj.run()
call_other_module.assert_called_once()
And also if you want to make sure that you did infact patch the target MyModule.will_call_other_package, modify your test like this:
class TestMyModule:
def test_run_will_call_other_package(self, call_other_module):
call_other_module.assert_not_called()
obj = MyModule()
call_other_module.assert_not_called()
obj.run()
call_other_module.assert_called_once()
assert False, (MyModule.will_call_other_package, call_other_module)
And you'll see something similar to this:
AssertionError: (<MagicMock name='will_call_other_package' id='140695551841328'>, <MagicMock name='will_call_other_package' id='140695551841328'>)
As you can see the id of both objects are the same, confirming our experiment was successful.
I have a conftest.py and a plugin, both defining the same fixture with different implementations:
conftest.py
import pytest
#pytest.fixture
def f():
yield 1
plugin
import pytest
#pytest.fixture
def f():
yield 2
when installing the plugin, the conftest still overrides the plugin, so a test file will only see the conftest fixture, i.e.
test_.py
def test(f):
assert f == 1 # True
I want to be able to do something like this:
If the plugin is not installed, continue
Else, from the conftest plugin, yield the value of the plugin's fixture
I managed to get half of the way:
conftest.py
import pytest
#pytest.fixture
def f(pytestconfig):
if pytestconfig.pluginmanager.has_plugin(plugin_name):
# now what? I have get_plugin and import_plugin, but I'm not able to get the fixture from there...
The easiest way I see is to try and get the plugin fixture value. If the fixture lookup fails, then no plugin defined it and you can do your own thing. Example:
import pytest
from _pytest.fixtures import FixtureLookupError
#pytest.fixture
def f(request):
try: # try finding an already declared fixture with that name
yield request.getfixturevalue('f')
except FixtureLookupError:
# fixture not found, we are the only fixture named 'f'
yield 1
Folks,
I have a problem during including file.py to test_file.py namely:
file.py uses Robot library BuiltIn:
from robot.libraries.BuiltIn import BuiltIn
DEFAULT_IPHY_TTI_TRACE_DIR =
os.path.join(BuiltIn().get_variable_value('${OUTPUT_DIR}'), 'iphy_tti_trace')
And when I try to include file.py in my test_file.py
import pytest
#import file.py
I receive:
test_file.py:8: in <module>
/opt/ute/python/lib/python2.7/site-packages/robot/libraries/BuiltIn.py:1331: in get_variable_value
return self._variables[self._get_var_name(name)]
/opt/ute/python/lib/python2.7/site-packages/robot/libraries/BuiltIn.py:75: in _variables
return self._namespace.variables
/opt/ute/python/lib/python2.7/site-packages/robot/libraries/BuiltIn.py:71: in _namespace
return self._get_context().namespace
/opt/ute/python/lib/python2.7/site-packages/robot/libraries/BuiltIn.py:66: in _get_context
raise RobotNotRunningError('Cannot access execution context')
E RobotNotRunningError: Cannot access execution context
How can I mock this? This is posible at all?
Sure, the issue is just that you can't mock the BuiltIn class where it is used (in file.py). You have to mock the class where it is declared (in robot.libraries.BuiltIn).
Using mocks:
from unittest.mock import patch, MagicMock
def _test_default_iphy_tti_trace_dir():
with patch('robot.libraries.BuiltIn.BuiltIn.get_variable_value', return_value='/foo/bar'):
import file
assert file.DEFAULT_IPHY_TTI_TRACE_DIR == '/foo/bar/iphy_tti_trace'
Using monkeypatch fixture:
def test_default_iphy_tti_trace_dir(monkeypatch):
def mocked_get(self, name):
return '/foo/bar'
monkeypatch.setattr('robot.libraries.BuiltIn.BuiltIn.get_variable_value', mocked_get)
import file
assert file.DEFAULT_IPHY_TTI_TRACE_DIR == '/foo/bar/iphy_tti_trace'
Also note that the mocking is done for the scope of a single test only, so you can't import file on top of the test module as the BuiltIn will be unpatched there, raising the context error.
So what I would like to achieve is mocking functions in various modules automatically with pytest. So I defined this in my conftest.py:
import sys
import __builtin__
from itertools import chain
# Fixture factory magic START
NORMAL_MOCKS = [
"logger", "error", "logging", "base_error", "partial"]
BUILTIN_MOCKS = ["exit"]
def _mock_factory(name, builtin):
def _mock(monkeypatch, request):
module = __builtin__ if builtin else request.node.module.MODULE
ret = Mock()
monkeypatch.setattr(module, name, ret)
return ret
return _mock
iterable = chain(
((el, False) for el in NORMAL_MOCKS),
((el, True) for el in BUILTIN_MOCKS))
for name, builtin in iterable:
fname = "mock_{name}".format(name=name)
_tmp_fn = pytest.fixture(name=fname)(_mock_factory(name, builtin))
_tmp_fn.__name__ = fname
setattr(
sys.modules[__name__],
"mock_{name}".format(name=name), _tmp_fn)
# Fixture normal factory magic END
This works and all, but I would like to omit the usage of the NORMAL_MOCKS and BUILTIN_MOCKS lists. So basically in a pytest hook I should be able to see that say there is a mock_foo fixture, but it's not registered yet, so I create a mock for it with the factory and register it. I just couldn't figure out how to do this. Basically I was looking into the pytest_runtest_setup function, but could not figure out how to do the actual fixture registration. So basically I would like to know with which hook/call can I register new fixture functions programatically from this hook.
One of the ways is to parameterize the tests at the collection/generation stage, i.e. before the test execution begins: https://docs.pytest.org/en/latest/example/parametrize.html
# conftest.py
import pytest
def mock_factory(name):
return name
def pytest_generate_tests(metafunc):
for name in metafunc.fixturenames:
if name.startswith('mock_'):
metafunc.parametrize(name, [mock_factory(name[5:])])
# test_me.py
def test_me(request, mock_it):
print(mock_it)
A very simple solution. But the downside is that the test is reported as parametrized when it actually is not:
$ pytest -s -v -ra
====== test session starts ======
test_me.py::test_me[it] PASSED
====== 1 passed in 0.01 seconds ======
To fully simulate the function args without the parametrization, you can make a less obvious trick:
# conftest.py
import pytest
def mock_factory(name):
return name
#pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item, nextitem):
for name in item.fixturenames:
if name.startswith('mock_') and name not in item.funcargs:
item.funcargs[name] = mock_factory(name[5:])
yield
The pytest_runtest_setup hook is also a good place for this, as long as I've just tried.
Note that you do not register the fixture in that case. It is too late for the fixture registration, as all the fixtures are gathered and prepared much earlier at the collection/parametrization stages. In this stage, you can only execute the tests and provide the values. It is your responsibility to calculate the fixture values and to destroy them afterward.
The snippet below is a pragmatic solution to "how to dynamically add fixtures".
Disclaimer: I don't have expertise on pytest. I'm not saying this is what pytest was designed for, I just looked at the source code and came up with this and it seems to work. The fact that I use "private" attributes means it might not work with all versions (currently I'm on pytest 7.1.3)
from _pytest.fixtures import FixtureDef
from _pytest.fixtures import SubRequest
import pytest
#pytest.fixture(autouse=True) # autouse is relevant, as then the fixture registration happens in-time. It's too late if requiring the fixture without autouse e.g. like `#pytest.mark.usefixtures("add_fixture_dynamically")`
def add_fixture_dynamically(request: SubRequest):
"""
Conditionally and dynamically adds another fixture. It's conditional on the presence of:
#pytest.mark.my_mark()
"""
marker = request.node.get_closest_marker("my_mark")
# don't register fixture if marker is not present:
if marker is None:
return
def your_fixture(): # the name of the fixture must match the parameter name, like other fixtures
return "hello"
# register the fixture just-in-time
request._fixturemanager._arg2fixturedefs[your_fixture.__name__] = [
FixtureDef(
argname=your_fixture.__name__,
func=your_fixture,
scope="function",
fixturemanager=request._fixturemanager,
baseid=None,
params=None,
),
]
yield # runs the test. Could be wrapped in try/except/finally
# suppress warning (works if this and `add_fixture_dynamically` are in `conftest.py`)
def pytest_configure(config):
"""Prevents printing of the warning 'PytestUnknownMarkWarning: Unknown pytest.mark.<fixture_name>'"""
config.addinivalue_line("markers", "my_mark")
#pytest.mark.my_mark()
def test_adding_fixture_dynamically(your_fixture):
assert your_fixture == "hello"