Python unit testing with Pytest and Mock
My favorite documentation is objective-based: I’m trying to achieve X objective, here are some examples of how library Y can help. The Pytest and Mock documentations include many examples, but the examples are not opinionated. It’s not clear which of 7 ways to achieve my objective is best.
Here are some examples I developed while contributing to the Neuroscience research platform BigNeuron. I’ve renamed the methods for clarity, but the original code can be found here: module and tests. (I’m actually using a wrapper library called Pytest-mock instead of plain Mock).
Directory structure that makes running tests easy
/rootdir
/src
/jobitems
api.py
constants.py
manager.py
models.py
tasks.py
/tests
/integ_tests
/jobitems
test_manager.py
/unit_tests
/jobitems
test_manager.py
requirements.py
application.py
How do I run these tests?
python -m pytest tests/ (all tests)
python -m pytest -k filenamekeyword (tests matching keyword)
python -m pytest tests/utils/test_sample.py (single test file)
python -m pytest tests/utils/test_sample.py::test_answer_correct (single test method)
python -m pytest --resultlog=testlog.log tests/ (log output to file)
python -m pytest -s tests/ (print output to console)
Import statements
import mock
import pytest
from pytest_mock import mocker
from src.jobitems import manager
Verify sub-method called with specific parameters
def test_update_jobs_fleet_capacity(mocker):
mocker.patch.object(manager, 'sub_method')
manager.sub_method.return_value = 120
manager.method_under_test()
manager.sub_method.assert_called_with('somestring', 1, 120)
Mock class in another module
def test_helper_class(mocker):
mocker.patch.object(manager, 'other_class')
manager.other_class.other_method.return_value = 50
manager.method_under_test('option1')
manager.other_class.other_method.assert_called_with(120)
Run test multiple times with array of params
@pytest.mark.parametrize('test_type, var1, var2, expect', [
('verify_max_returned', 'apple', 90, 100),
('verify_min_returned', 'orange', 2, 10)
])
def test_get_size(mocker, test_type, var1, var2, expect):
print("Logging test type for visibility: " + test_type)
assert fleet_manager.get_optimal_size('zone1', var1,
var2) == expect
Verify method is called with any parameter (value doesn’t matter)
manager.update_size(mock.ANY, 'var2', mock.ANY)
Override constant in config file
def test_update_config_val(monkeypatch):
monkeypatch.setattr(config, 'WEBSITE_URL', 'mysite.com')
assert manager.method_that_uses_config_val() == 'mysite.com'
Override environment variable
def test_update_env_var(monkeypatch):
monkeypatch.setenv('DOMAIN', 'Devo')
assert manager.method_that_uses_env_var() == 'Devo'
Verify unit test fails
@pytest.mark.xfail(raises=Exception)
def test_that_should_fail():
method_that_throws_Exception()
Verify test throws exception
def test_that_should_throw_exception():
with pytest.raises(Exception):
method_that_throws_Exception()
Skip test that fails intermittently
@pytest.mark.skipif(True, reason="Method is too slow")
def test_very_slow_method():
this_method_is_too_slow(19209938)
Create shared Mock object for all tests in module
@pytest.fixture(scope="module")
def my_fixture():
return SHARED_OBJECT_OR_VARIABLEdef test_that_uses_shared_mock_obj(my_fixture):
assert manager.do_something(my_fixture, 'hello') == None
That’s it! I hope these examples help.
UPDATE June 2017: Check out Wil Cooley’s response below. He offers some helpful corrections and points out better ways to accomplish some of the tasks above. Thanks, Wil!