Metadata-Version: 2.4
Name: seecr-testtools
Version: 0.4.2
Summary: Test tools for seecr
Author-email: Thijs Janssen <thijs@seecr.nl>, Johan Jonkers <johan@seecr.nl>
License-Expression: GPL-3.0-or-later
Project-URL: Homepage, https://github.com/seecr/seecr-testtools
Project-URL: Source, https://github.com/seecr/seecr-testtools
Project-URL: Issues, https://github.com/seecr/seecr-testtools/issues
Keywords: test,pytest
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Framework :: Pytest
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pytest<10,>=9.0.3
Requires-Dist: pytest-asyncio<2,>=1.3.0
Dynamic: license-file

# Seecr Testtools

Small helpers for writing pytest tests, used across [Seecr](https://seecr.nl) projects.

[![Tests](https://github.com/seecr/seecr-testtools/actions/workflows/test.yml/badge.svg)](https://github.com/seecr/seecr-testtools/actions/workflows/test.yml)
[![PyPI](https://img.shields.io/pypi/v/seecr-testtools.svg)](https://pypi.org/project/seecr-testtools/)

## Installation

```sh
pip install seecr-testtools
```

Requires Python 3.11 or newer. Installs `pytest` and `pytest-asyncio`.

## What's inside

### `anything`: wildcards in assertions

`anything` equals every value, so you can compare whole structures while
ignoring the parts you don't care about. Pass a function to check a value
instead of ignoring it.

```python
from seecr_testtools import anything

assert response == {
    "id": anything,                           # any value
    "name": "Alice",
    "age": anything(lambda age: age >= 18),   # must satisfy the check
}
```

### `Mock`: record calls and fake return values

```python
from seecr_testtools.mock import Mock

store = Mock.blank(get={"a": 1}.get, save=None)
store.get("a")              # 1: a callable value is called
store.save(1, force=True)   # None: other values are returned as is
store.whatever()            # unknown methods return None
store.calls("save")         # [save(1, force=True)]
store.calls("save")[0].kwargs  # {'force': True}
```

`Mock.imitate(obj)` wraps a real object. Methods you override with
`mock_method` are replaced on the object itself, so the object's own code
calls the fake too. Coroutine methods automatically get an async fake.

```python
class Repository:
    def load(self):
        return "from database"

    def report(self):
        return self.load().upper()

repo = Repository()
mock = Mock.imitate(repo)
mock.mock_method("load", "fake")

assert mock.report() == "FAKE"   # report() runs for real, load() is faked
assert len(mock.calls("load")) == 1
```

For a blank mock, use `mock_method(name, value, is_async=True)` to get an
async method.

### `login_user`

Logs a user in on a test client for Seecr web applications and keeps the
session cookie: `login_user(env, username, password)`. It expects `env` to
provide `client` and `actions`.

## Development

```sh
python -m venv env --prompt seecr-testtools
source env/bin/activate
pip install -e .
pytest
```

Tests live next to the code as `<module>_test.py`. Async tests need no
decorator: pytest-asyncio runs in auto mode.

## License

Copyright (C) 2024-2026 Seecr (Seek You Too B.V.)

Licensed under the [GNU General Public License v3.0 or later](LICENSE).
