Testing
Ascender Framework provides a comprehensive testing system built on pytest, with utilities for dependency injection mocking, test lifecycle management, and data generation.
Getting Started
Installation
Tests are located in src/tests/ by default. The framework provides built-in testing utilities through the ascender.testing module.
Running Tests
Use the local CLI wrapper to run tests:
# Run all tests
ascender run tests
# Run with verbose output
ascender run tests --verbose
# Run with specific marks
ascender run tests -m "unit"
Initialize Testing Environment
The framework can scaffold a basic testing environment (planned feature):
This command will:
- Create src/tests/directory structure
- Generate sample test files (conftest.py,test_initial.py)
- Create pytest.iniconfiguration file
Test Configuration
pytest.ini
The pytest.ini file configures pytest behavior:
[pytest]
testpaths = src/tests
python_files = test_*.py
python_classes = Test* *Tests
python_functions = test_*
addopts = -v --tb=short --disable-warnings
log_cli = true
log_level = INFO
asyncio_mode = auto
conftest.py
The conftest.py file sets up the test lifecycle:
import pytest
from ascender.testing import AscenderTestLifecycle
# Initialize lifecycle with your app module or providers
lifecycle = AscenderTestLifecycle(providers=[])
def pytest_sessionstart(session: pytest.Session):
    """Initialize framework at session start."""
    lifecycle.begin_session(session)
@pytest.fixture(scope="function", autouse=True)
def ascender_app():
    """Provide application instance to tests."""
    lifecycle.before_test()
    yield lifecycle.application
    lifecycle.after_test()
def pytest_sessionfinish(session: pytest.Session, exitstatus: int):
    """Cleanup at session end."""
    lifecycle.end_session()
Testing Utilities
AscenderTestLifecycle
Manages the test lifecycle and application initialization:
from ascender.testing import AscenderTestLifecycle
from app_module import AppModule
# With app module
lifecycle = AscenderTestLifecycle(app_module=AppModule)
# With custom providers
lifecycle = AscenderTestLifecycle(providers=[
    MyService,
    # other providers...
])
TestClient
Create test instances with mocked dependencies:
from ascender.testing import TestClient, MockDependency
from controllers.user_controller import UserController
from services.user_service import UserService
def test_user_controller():
    # Mock the service
    controller = TestClient(
        UserController,
        mocks={
            UserService: MockDependency(
                get_user=lambda self, id: {"id": id, "name": "Test User"}
            )
        }
    )
    # Test the controller
    result = await controller.get_user(1)
    assert result["name"] == "Test User"
Mixer
Generate test data for Pydantic models:
from ascender.testing import Mixer, FakerField
from pydantic import BaseModel
from typing import Annotated
class User(BaseModel):
    name: str
    email: Annotated[str, FakerField("email")]
    age: int
# Create mixer
mixer = Mixer(enable_auto_faker=True)
# Generate single instance
user = mixer.blend(User)
# Generate multiple instances
users = mixer.blend_many(User, count=5)
# With overrides
user = mixer.blend(User, name="John Doe")
MockDependency
Create mock objects for testing:
from ascender.testing import MockDependency
# Create mock
mock_service = MockDependency(
    get_data=lambda self: "mocked data",
    process=lambda self, value: f"processed: {value}",
    config={"setting": "value"}
)
# Use as object
mock_obj = mock_service._as_object("MockService")
print(mock_obj.get_data())  # "mocked data"
Writing Tests
Basic Test Example
from ascender.core import Application, inject, TestInjector
def test_application_boots(ascender_app: Application):
    """Test that application initializes correctly."""
    assert ascender_app.app is not None
    assert ascender_app.is_ok()
Testing with Dependency Injection
from ascender.core import inject
from services.my_service import MyService
def test_service_injection():
    """Test that services can be injected."""
    service = inject(MyService)
    assert isinstance(service, MyService)
Testing Controllers
from ascender.testing import TestClient, MockDependency
from controllers.user_controller import UserController
from services.user_service import UserService
def test_user_controller_get_all():
    """Test controller with mocked service."""
    controller = TestClient(
        UserController,
        mocks={
            UserService: MockDependency(
                get_all=lambda self: [
                    {"id": 1, "name": "User 1"},
                    {"id": 2, "name": "User 2"}
                ]
            )
        }
    )
    result = await controller.get_all_users()
    assert len(result) == 2
Testing with Faker
from ascender.testing import Mixer, FakerField
from typing import Annotated
from pydantic import BaseModel
class UserDTO(BaseModel):
    username: Annotated[str, FakerField("user_name")]
    email: Annotated[str, FakerField("email")]
    city: Annotated[str, FakerField("city")]
def test_user_creation():
    """Test user creation with generated data."""
    mixer = Mixer(enable_auto_faker=True)
    user_data = mixer.blend(UserDTO)
    # Data is automatically generated
    assert "@" in user_data.email
    assert len(user_data.username) > 0
Testing Modules
Create isolated testing modules:
from ascender.testing import TestClient
from ascender.core import inject
def test_module_creation():
    """Test creating a testing module."""
    module_ref = TestClient.create_testing_module(
        imports=[],
        declarations=[UserController],
        providers=[UserService],
        exports=[],
        name="TestModule"
    )
    # Use the injector to get dependencies
    service = inject(UserService, scope=module_ref)
    assert service is not None
Best Practices
- Use Fixtures: Leverage pytest fixtures for common setup
- Mock External Dependencies: Use MockDependencyfor external services
- Test Isolation: Each test should be independent
- Use Faker for Data: Generate realistic test data with Mixer
- Test DI: Verify dependency injection works correctly
- Async Tests: Use async deffor async endpoint tests
- Mark Tests: Use pytest marks to organize tests (@pytest.mark.unit)
Next Steps
- Explore Dependency Injection for better understanding of DI testing
- Learn about Controllers to test endpoints effectively
- Check out pytest documentation for advanced testing patterns