Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Guide for using pytest-mock plugin to write tests with mocking. Use when writing pytest tests that need mocking, patching, spying, or stubbing. Covers mocker fixture usage, patch methods, spy/stub patterns, and assertion helpers.
.claude/skills/aiskillstore-pytest-mock-guide/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 42% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 46% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 30% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 180% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 121% | 0% |
pytest-mock is a pytest plugin providing a mocker fixture as a thin wrapper around Python's unittest.mock patching API. It automatically undoes all mocking at the end of each test.
The mocker fixture is the main interface. Request it in your test function:
pythondef test_example(mocker): # All mocks are automatically cleaned up after this test mock_func = mocker.patch("module.function")
| Fixture | Scope | Use Case | |---------|-------|----------| | mocker | function | Default, per-test mocking | | class_mocker | class | Share mocks across test class | | module_mocker | module | Share mocks across test module | | package_mocker | package | Share mocks across package | | session_mocker | session | Share mocks across entire session |
Patch a module-level object by its dotted path:
pythondef test_patch(mocker): # Patch os.remove function mock_remove = mocker.patch("os.remove") mock_remove.return_value = None os.remove("file.txt") mock_remove.assert_called_once_with("file.txt")
Patch an attribute on an object directly:
pythondef test_patch_object(mocker): import os mock_remove = mocker.patch.object(os, "remove") os.remove("file.txt") mock_remove.assert_called_once_with("file.txt")
Patch a dictionary temporarily:
pythondef test_patch_dict(mocker): config = {"debug": False} mocker.patch.dict(config, {"debug": True}) assert config["debug"] is True # After test, config["debug"] is False again
Patch multiple attributes at once:
pythondef test_patch_multiple(mocker): mocks = mocker.patch.multiple( "os", remove=mocker.DEFAULT, listdir=mocker.DEFAULT ) os.remove("file.txt") os.listdir("/tmp") mocks["remove"].assert_called_once() mocks["listdir"].assert_called_once()
Same as patch.object but doesn't warn when mock is used as context manager:
pythondef test_context_manager(mocker): mock_open = mocker.patch.context_manager(builtins, "open") # No warning when using `with mock_open(...)`
| Parameter | Description | |-----------|-------------| | new | Object to replace target with | | return_value | Value returned when mock is called | | side_effect | Exception to raise or function to call | | autospec | Create mock matching target's signature | | spec | Object to use as specification | | spec_set | Stricter spec that prevents setting new attributes | | create | Allow patching non-existent attributes | | new_callable | Callable to create the mock |
Spy wraps the real method while tracking calls:
pythondef test_spy(mocker): spy = mocker.spy(os.path, "exists") # Real method is called result = os.path.exists("/tmp") # But we can inspect calls spy.assert_called_once_with("/tmp") # Access return values assert spy.spy_return == result assert spy.spy_return_list == [result] # All returns
| Attribute | Description | |-----------|-------------| | spy_return | Last return value from real method | | spy_return_list | List of all return values | | spy_return_iter | Iterator copy (when duplicate_iterators=True) | | spy_exception | Last exception raised, if any |
pythondef test_spy_iterator(mocker): spy = mocker.spy(obj, "get_items", duplicate_iterators=True) items = list(obj.get_items()) # Access a copy of the returned iterator spy_items = list(spy.spy_return_iter)
Create a stub that accepts any arguments:
pythondef test_stub(mocker): callback = mocker.stub(name="my_callback") some_function(on_complete=callback) callback.assert_called_once()
Create an async stub:
pythonasync def test_async_stub(mocker): callback = mocker.async_stub(name="async_callback") await some_async_function(on_complete=callback) callback.assert_awaited_once()
Create a mock that matches the spec's signature:
pythondef test_autospec(mocker): mock_obj = mocker.create_autospec(MyClass, instance=True) # Calling with wrong arguments raises TypeError mock_obj.method() # OK if method() takes no args
Access mock classes directly through mocker:
pythondef test_mock_classes(mocker): mock = mocker.Mock() magic_mock = mocker.MagicMock() async_mock = mocker.AsyncMock() property_mock = mocker.PropertyMock() non_callable = mocker.NonCallableMock()
pythondef test_utilities(mocker): # Match any argument mock.assert_called_with(mocker.ANY) # Create call objects for assertion mock.assert_has_calls([mocker.call(1), mocker.call(2)]) # Sentinel objects result = mocker.sentinel.my_result # Mock file open m = mocker.mock_open(read_data="file contents") mocker.patch("builtins.open", m) # Seal a mock to prevent new attributes mocker.seal(mock)
Stop all patches immediately:
pythondef test_stopall(mocker): mocker.patch("os.remove") mocker.patch("os.listdir") mocker.stopall() # Both patches stopped
Stop a specific patch:
pythondef test_stop(mocker): mock_remove = mocker.patch("os.remove") mocker.stop(mock_remove) # Only this patch stopped
Reset all mocks without stopping them:
pythondef test_resetall(mocker): mock_func = mocker.patch("module.func") mock_func("arg1") mocker.resetall() mock_func.assert_not_called() # Call history cleared
pytest-mock enhances assertion error messages with pytest's comparison:
pythondef test_assertions(mocker): mock = mocker.patch("module.func") mock("actual_arg") # Enhanced error shows diff between expected and actual mock.assert_called_with("expected_arg") # AssertionError shows: # Args: # assert ('actual_arg',) == ('expected_arg',)
Call Assertions:
assert_called() - Called at least onceassert_called_once() - Called exactly onceassert_called_with(*args, **kwargs) - Last call matchesassert_called_once_with(*args, **kwargs) - Called once with argsassert_any_call(*args, **kwargs) - Any call matchesassert_has_calls(calls, any_order=False) - Has specific callsassert_not_called() - Never calledAsync Assertions (for AsyncMock):
assert_awaited()assert_awaited_once()assert_awaited_with(*args, **kwargs)assert_awaited_once_with(*args, **kwargs)assert_any_await(*args, **kwargs)assert_has_awaits(calls, any_order=False)assert_not_awaited()In pytest.ini, pyproject.toml, or setup.cfg:
ini[pytest] # Enable/disable enhanced assertion messages (default: true) mock_traceback_monkeypatch = true # Use standalone mock package instead of unittest.mock (default: false) mock_use_standalone_module = false
python# my_module.py from os.path import exists def check_file(path): return exists(path) # test_my_module.py def test_check_file(mocker): # Patch where it's used, not where it's defined mocker.patch("my_module.exists", return_value=True) assert check_file("/any/path") is True
pythondef test_exception(mocker): mock_func = mocker.patch("module.func") mock_func.side_effect = ValueError("error message") with pytest.raises(ValueError, match="error message"): module.func()
pythondef test_multiple_returns(mocker): mock_func = mocker.patch("module.func") mock_func.side_effect = [1, 2, 3] assert module.func() == 1 assert module.func() == 2 assert module.func() == 3
pythonasync def test_async(mocker): mock_fetch = mocker.patch("module.fetch_data") mock_fetch.return_value = {"data": "value"} result = await module.fetch_data() assert result == {"data": "value"}
pythondef test_class_method(mocker): mocker.patch.object(MyClass, "class_method", return_value="mocked") assert MyClass.class_method() == "mocked"
pythondef test_property(mocker): mock_prop = mocker.patch.object( MyClass, "my_property", new_callable=mocker.PropertyMock, return_value="mocked" ) obj = MyClass() assert obj.my_property == "mocked"
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 16,798 | 13,095 | -22% | 1 | 1 | 0% | 3,034 | 4,314 | +42% | 0 | 0 | — |
case-02 | fail→pass | 17,608 | 16,313 | -7% | 1 | 1 | 0% | 3,268 | 4,778 | +46% | 0 | 0 | — |
case-03 | pass→pass | 16,216 | 5,452 | -66% | 1 | 1 | 0% | 1,713 | 3,638 | +112% | 0 | 0 | — |
case-04 | fail→pass | 22,125 | 6,521 | -71% | 1 | 1 | 0% | 3,046 | 3,945 | +30% | 0 | 0 | — |
case-05 | fail→pass | 10,902 | 4,469 | -59% | 1 | 1 | 0% | 1,236 | 3,461 | +180% | 0 | 0 | — |
case-06 | pass→pass | 4,922 | 3,217 | -35% | 1 | 1 | 0% | 916 | 3,201 | +249% | 0 | 0 | — |
case-07 | pass→pass | 12,944 | 4,279 | -67% | 1 | 1 | 0% | 1,372 | 3,320 | +142% | 0 | 0 | — |
case-08 | fail→pass | 9,190 | 6,339 | -31% | 1 | 1 | 0% | 1,713 | 3,782 | +121% | 0 | 0 | — |
case-09 | fail→pass | 19,030 | 12,987 | -32% | 1 | 1 | 0% | 2,373 | 4,095 | +73% | 0 | 0 | — |
case-10 | fail→pass | 13,749 | 4,426 | -68% | 1 | 1 | 0% | 1,672 | 3,421 | +105% | 0 | 0 | — |
case-11 | fail→pass | 7,456 | 9,554 | +28% | 1 | 1 | 0% | 1,332 | 3,519 | +164% | 0 | 0 | — |
case-12 | pass→pass | 13,257 | 9,191 | -31% | 1 | 1 | 0% | 1,441 | 3,394 | +136% | 0 | 0 | — |
case-13 | pass→pass | 13,788 | 5,571 | -60% | 1 | 1 | 0% | 1,637 | 3,645 | +123% | 0 | 0 | — |
case-14 | pass→pass | 8,531 | 11,258 | +32% | 1 | 1 | 0% | 1,825 | 3,945 | +116% | 0 | 0 | — |
case-15 | pass→pass | 12,800 | 4,001 | -69% | 1 | 1 | 0% | 1,530 | 3,365 | +120% | 0 | 0 | — |
case-16 | fail→pass | 20,955 | 11,702 | -44% | 1 | 1 | 0% | 2,137 | 3,997 | +87% | 0 | 0 | — |
case-17 | pass→pass | 12,420 | 7,616 | -39% | 1 | 1 | 0% | 1,417 | 3,158 | +123% | 0 | 0 | — |
case-18 | pass→pass | 10,838 | 9,188 | -15% | 1 | 1 | 0% | 1,038 | 3,422 | +230% | 0 | 0 | — |
case-19 | fail→pass | 16,171 | 7,012 | -57% | 1 | 1 | 0% | 2,050 | 3,960 | +93% | 0 | 0 | — |
case-20 | pass→pass | 9,750 | 3,548 | -64% | 1 | 1 | 0% | 823 | 3,306 | +302% | 0 | 0 | — |
case-21 | pass→pass | 12,417 | 5,485 | -56% | 1 | 1 | 0% | 1,422 | 3,720 | +162% | 0 | 0 | — |
case-22 | pass→pass | 5,920 | 9,340 | +58% | 1 | 1 | 0% | 1,166 | 3,511 | +201% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +45 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.