Testing
Running the tests
The suites in tests/ (test_fixes.py, test_connection.py, test_api.py) are fully
offline — no MAL connection required. The live account tests in tests/account_objects/
are skipped automatically unless API access to a real account is configured:
| Variable | |
|---|---|
MAL_USERNAME |
the account name |
MAL_CLIENT_ID |
client ID of your API app (apiconfig) |
MAL_ACCESS_TOKEN |
an OAuth access token — optional if you ran Account.authorize() before (the saved token file is used) |
MAL_SESSIONID |
the MALSESSIONID cookie, instead of the API variables (experimental; only status, score and progress are tested) |
MAL_TEST_WRITE=1 |
also run the tests that add, change and delete entries (ADD_ANIME_ID / ADD_MANGA_ID in tests/constants_for_testing.py must not be in the list) |
# Run all tests
uv run pytest
# Verbose output
uv run pytest -v
# Specific file
uv run pytest tests/test_fixes.py -v
Structure
tests/
├── test_fixes.py # Parsing, guards, imports, object behaviour
├── test_connection.py # Retries and "site unavailable" handling, helpers
├── test_api.py # OAuth, API client, account lists, MyAnime/MyManga (mocked API)
├── constants_for_testing.py # Shared test constants and live-test credentials
└── account_objects/ # Live account tests (skipped without credentials)
├── test_live_account.py
└── test_account_friends.py
Guidelines
New tests should be offline (except the ones in account_objects/). Use unittest.mock.patch and inline HTML fixtures
instead of real HTTP calls or saved HTML snapshots.
from unittest.mock import patch, MagicMock
def make_response(html: str) -> MagicMock:
mock = MagicMock()
mock.text = html
return mock
with patch("pymal.global_functions.session") as mock_session:
mock_session.get.return_value = make_response("<html>...</html>")
anime.reload()
Keep assertions informative — use the msg= parameter or structure your
assertions so failures clearly identify what broke.
Shared constants go in tests/constants_for_testing.py.
Test-local constants go inside the TestCase class.