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

  1. Isolate tests: Each test should be independent of others.

  2. Mocks for external integrations: Replace databases, HTTP clients, etc. with stubs and dishka providers.

  3. Cover error scenarios: Incorrect flags, unknown commands, empty input.

  4. Minimize formatting dependency: Compare key output fragments, not the entire block.

  5. Measure coverage: Use pytest-cov.