DEV Community

Mišo
Mišo

Posted on • Updated on

Any advanced alternative to unitest.mock.ANY?

I hope for the comment from somebody with a tool or the technique on how to assert on container structures in a readable way. Here is an example I want to talk about.

from unittest.mock import ANY

import pytest


def test_chatbot_response_is_polite():
    data = generate_data()

    assert data == {
        "id": ANY,
        "confidence": pytest.approx(0.5, abs=0.5),
        "text": "Hi, I am chatbot :)",
        "responses": ANY.type_(list[str]),  # this does not exist, but I need it :)
    }
Enter fullscreen mode Exit fullscreen mode

As you can see I use pytest. The thing is that sometimes I need to assert not only on specific things but also on the structure of the data (dict, list, tuple, ...) with some constraints.

If I don't care about the specific field or argument of the mock call I can ignore it with the help of unittest.mock.ANY. You can see this for the id field. If the field is a number I can "misuse" pytest.approx() function as for the field confidence where I don't care about the particular value but I know it should always be in the interval (0; 1).

But sometimes I would like to say only, the field responses is any list of strings. Or, it is any container with len(container) == 1 aka ANY.len(1). I know Nette tester has Expectations and Jest has expect.any(Number)/expect.anything() but is there anything like this in Python?

Also, I know there are libs like voluptuous or schema but these are not designed for these ad-hoc comparisons. And don't fit into the use-case with asserting nicely. Maybe I am wrong and I should use them in tests too.

The closest library I found is assertpy. I believe there has to be a much simpler alternative so I can use the nice plain assert (see the tweet below) and sometimes add an escape hatch like this.

The simplest primitive I could build looks like below. I could build upon it a library, but don't even know if there is not a better way.

class ExpectPredicate:
    def __init__(self, predicate: Callable[[Any], bool]):
        self._predicate = predicate

    def __eq__(self, other):
        return self._predicate(other)


def test_chatbot_response_is_polite():
    data = generate_data()

    assert data == {
        "id": ANY,
        "confidence": pytest.approx(0.5, abs=0.5),
        "text": "Hi, I am chatbot :)",
        "responses": ExpectPredicate(lambda r: isinstance(r, list) and all(isinstance(i, str) for i in r)),
    }
Enter fullscreen mode Exit fullscreen mode

And my dream API:

from unknown_library import expect


def test_chatbot_response_is_polite():
    data = generate_data()

    assert data == {
        "id": expect.type(str),
        "confidence": expect.range(0, 1),
        "text": "Hi, I am chatbot :)",
        "responses": expect.type(list[str]),
    }
Enter fullscreen mode Exit fullscreen mode

EDIT: It seems the pytest-voluptuous does the trick and even gives me a nice error message:

from pytest_voluptuous import S
from voluptuous import All, Range


def test_chatbot_response_is_polite():
    data = generate_data()

    assert data == S({
        "id": str,
        "confidence": All(float, Range(min=0, max=1)),
        "text": "Hi, I am chatbot :)",
        "responses": [str],
    })
Enter fullscreen mode Exit fullscreen mode

Top comments (0)