Testing¶
This section describes testing practices for applications based on Argenta. Examples are based on the actual public API.
Unit Testing Handlers¶
Handlers in Argenta are regular functions. They are convenient to test as pure functions without starting the entire application cycle. unittest or pytest are recommended.
Usage example:
1import io
2from contextlib import redirect_stdout
3
4from argenta import Router, Command, Response
5from argenta.command import InputCommand
6
7
8router = Router(title="Demo")
9
10
11@router.command(Command("PING", description="Ping command"))
12def ping(response: Response):
13 print("PONG")
14
15
16def test_ping_prints_pong():
17 # Call handler
18 with redirect_stdout(io.StringIO()) as stdout:
19 router.finds_appropriate_handler(InputCommand.parse("PING"))
20 assert "PONG" in stdout.getvalue()
Testing with Dependency Injection (DI)¶
If a handler needs dependencies, use dishka and Argenta integration:
Usage example:
1import io
2from contextlib import redirect_stdout
3
4from argenta.command import InputCommand
5from dishka import Provider, make_container, Scope
6
7from argenta import Router, Response
8from argenta.di.integration import setup_dishka, FromDishka
9
10
11class Service:
12 def hello(self) -> str:
13 return "world"
14
15
16def get_service() -> Service:
17 return Service()
18
19
20router = Router(title="DI")
21
22
23@router.command("HELLO")
24def hello(response: Response, service: FromDishka[Service]) -> None:
25 print(f"hello {service.hello()}")
26
27
28class _FakeApp:
29 # Minimal stub for setup_dishka; app object is not used in unit tests
30 registered_routers = [router]
31
32
33def test_hello_uses_service():
34 provider = Provider(scope=Scope.APP)
35 provider.provide(get_service)
36
37 container = make_container(provider)
38 setup_dishka(app=_FakeApp(), container=container, auto_inject=True)
39
40 # Call handler
41 with redirect_stdout(io.StringIO()) as stdout:
42 router.finds_appropriate_handler(InputCommand.parse("HELLO"))
43
44 assert "hello world" in stdout.getvalue()
Integration Testing of the Application¶
For higher-level tests, assemble App and Router and call handlers through command parsing, bypassing the infinite input loop. This provides behavior close to reality without the need to simulate stdin.
Usage example:
1import io
2from contextlib import redirect_stdout
3
4from argenta import App, Router, Command, Response
5from argenta.command import InputCommand
6
7
8def test_simple_app() -> None:
9 app = App(override_system_messages=True, repeat_command_groups_printing=False)
10 router = Router(title="App")
11
12 @router.command(Command("HELP", description="Show help"))
13 def help_cmd(response: Response):
14 print("Available commands: HELP")
15
16 app.include_router(router)
17
18 with redirect_stdout(io.StringIO()) as stdout:
19 router.finds_appropriate_handler(InputCommand.parse("HELP"))
20
21 assert "Available commands:" in stdout.getvalue()
E2E Testing of the Loop¶
Full execution of the start_polling loop can be covered through a subprocess with passing strings to stdin. This is heavier and usually not required. If still necessary, an example is below.
Danger
Important: Always pass the exit command string trigger as the last element in the side_effects list when patching input.
Otherwise, the application under test will wait for the next command input and will not be able to terminate correctly.
Usage example:
1import sys
2from unittest.mock import patch
3import pytest
4from pytest import CaptureFixture
5
6from argenta import App, Orchestrator, Router, Command, Response
7
8
9@pytest.fixture(autouse=True)
10def patched_argv():
11 with patch.object(sys, "argv", ["program.py"]):
12 yield
13
14
15def test_input_incorrect_command(capsys: CaptureFixture[str]):
16 router = Router()
17 orchestrator = Orchestrator()
18
19 @router.command(Command("test"))
20 def test(response: Response) -> None:
21 print("test command")
22
23 app = App(override_system_messages=True, printer=print)
24 app.include_router(router)
25 app.set_unknown_command_handler(lambda command: print(f"Unknown command: {command.trigger}"))
26
27 with patch("builtins.input", side_effect=["help", "q"]):
28 orchestrator.start_polling(app)
29
30 output = capsys.readouterr().out
31 assert "\nUnknown command: help\n" in output
Testing Tips¶
Isolate tests: Each test should be independent of others.
Mocks for external integrations: Replace databases, HTTP clients, etc. with stubs and
dishkaproviders.Cover error scenarios: Incorrect flags, unknown commands, empty input.
Minimize formatting dependency: Compare key output fragments, not the entire block.
Measure coverage: Use
pytest-cov.