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_VARIABLE
def 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!