Any advanced alternative to unitest.mock.ANY?
Tags: python, testing, suggestion, discuss
Published: 2020-05-01 14:26:48

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.

```python
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 :)
    }
```

As you can see I use [pytest](https://docs.pytest.org/). 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`](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.ANY). You can see this for the `id` field. If the field is a number I can "misuse" [`pytest.approx()`](https://docs.pytest.org/en/latest/reference.html#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](https://tester.nette.org/en/assertions#toc-expectations) and [Jest has `expect.any(Number)`/`expect.anything()`](https://jestjs.io/docs/en/expect#expectanyconstructor) but **is there anything like this in Python?**

Also, I know there are libs like [voluptuous](https://pypi.org/project/voluptuous/) or [schema](https://pypi.org/project/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](https://github.com/assertpy/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.

{% twitter 1101959314116210688 %}

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.
```python
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)),
    }
```

And my dream API:
```python
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]),
    }
```

EDIT: It seems the [pytest-voluptuous](https://github.com/F-Secure/pytest-voluptuous) does the trick and even gives me a nice error message:
```python
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],
    })
```