Skip to main content

Utility which makes mocking more readable and controllable

Project description

PyPI Build Supported Python Versions Ruff

pytest-when

Inspired by mokito-scala, pytest-when provides a fixture for pytest to simplify the mocking of python objects:

Purpose

More readable than the full Given...When...Then pattern, pytest-when is meant for developers who want to test for behaviour, without any extra overhead. Enable the mock only for a specific argument's values to make code more readable.

Benefits

In this example, when you specify the first two arguments and any third argument, the attribute will be mocked,

(
    when(some_object, "attribute")
    .called_with(1, 2, when.markers.any)
    .then_return("attribute mocked")
    # or
    # .then_call(some_callable)
    # or
    # .then_raise(SomeException())
)

Note that the .called_with method arguments are compared with the real callable signature. This gives additional protection against changing the real callable interface.

With when fixture, the following expression:

when(example_module, "some_normal_function").called_with(
    "a",
    1,
    kwarg1="b",
    kwarg2=2,
).then_return("Mocked")

is roughly equal to:

mock_only_for_calls = (
    call("a", 1, kwarg1="b", kwarg2=2),
    "Mocked",
)

def matches(
        call: Call,
        mocked_calls_registry: tuple[tuple[call, ReturnType], ...],
) -> bool:
    ...

def create_call(*args, **kwargs) -> Call:
    ...

def side_effect_callback(*args, **kwargs):
    if matches(create_call(*args, **kwargs), mock_only_for_calls):
        return mock_only_for_calls[1]
    return unittest.mock.DEFAULT


mocker.patch.object(
    example_module,
    "some_normal_function",
    autospec=True,
    side_effect=side_effect_callback,
)

where logic of matches and create_call is not trivial

Installation

Install the package into your development environment, from pypi, using pip, for example:

pip install pytest-when

Implementation

Onced installed, the when fixture will be available just like the rest of the pytest plugins. See the following example of how to use it:

# class which we're going to mock in the test
class Klass1:
    def some_method(
            self,
            arg1: str,
            arg2: int,
            *,
            kwarg1: str,
            kwarg2: str,
    ) -> str:
        return "some_method not mocked"


def test_should_properly_patch_calls(when):
    when(Klass1, "some_method").called_with(
        "a",
        when.markers.any,
        kwarg1="b",
        kwarg2=when.markers.any,
    ).then_return("some method mocked")

    assert (
            Klass1().some_method(
                "a",
                1,
                kwarg1="b",
                kwarg2="c",
            )
            == "some method mocked"
    )
    assert (
            Klass1().some_method(
                "not mocked param",
                1,
                kwarg1="b",
                kwarg2="c",
            )
            == "some method not mocked"
    )


# if you need to patch a function
def test_patch_a_function(when):
    when(example_module, "some_normal_function").called_with(
        "a",
        when.markers.any,
        kwarg1="b",
        kwarg2=when.markers.any,
    ).then_return("some_normal_function mocked")

    assert (
            example_module.some_normal_function(
                "a",
                1,
                kwarg1="b",
                kwarg2="c",
            )
            == "some_normal_function mocked"
    )
    assert (
            example_module.some_normal_function(
                "not mocked param",
                1,
                kwarg1="b",
                kwarg2="c",
            )
            == "some_normal_function not mocked"
    )

It is possible to use when with class methods and standalone functions (in this case cls parameter will become a python module).

You can patch the same object multiple times using different called_with parameters in a single test.

You can also patch multiple targets (cls, method)

See more examples at: test_integration

Setup for local developement

The project can be extended by cloning the repo and installing the PDM build tool So, the development environment requires:

  1. pdm https://pdm.fming.dev/latest/#installation
  2. python3.8 or greater
pdm install

To run tests and linters use:

make test
make lint

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest_when-2.2.0.tar.gz (10.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytest_when-2.2.0-py3-none-any.whl (7.9 kB view details)

Uploaded Python 3

File details

Details for the file pytest_when-2.2.0.tar.gz.

File metadata

  • Download URL: pytest_when-2.2.0.tar.gz
  • Upload date:
  • Size: 10.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.25.9 CPython/3.12.3 Linux/6.11.0-1018-azure

File hashes

Hashes for pytest_when-2.2.0.tar.gz
Algorithm Hash digest
SHA256 48927e5ce3081d56a888b416fcb24c59eda95fa145915ed61c7d0d9e5719222d
MD5 4c8fd9e51136f7f87990e659b8fae802
BLAKE2b-256 9e67ae3ca9bbcec1eed4280071f243f762b912c57e8b58c55e765afb1da4aa01

See more details on using hashes here.

File details

Details for the file pytest_when-2.2.0-py3-none-any.whl.

File metadata

  • Download URL: pytest_when-2.2.0-py3-none-any.whl
  • Upload date:
  • Size: 7.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.25.9 CPython/3.12.3 Linux/6.11.0-1018-azure

File hashes

Hashes for pytest_when-2.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6d15be5ad1dc5beac0a2195aa929274c43020d310e7c5311aee2697f53ab30fe
MD5 67a0cf9ad277b0602a5a33c8285a92b3
BLAKE2b-256 c5de8d41f2932fbf280cda2a2c4ae0e6f5b1ac94def655020d02be6ba783e3fb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page