Dependency Injection API Reference¶
StarStreamer's dependency injection system provides clean, testable code architecture by automatically resolving service dependencies for event handlers. The system supports both singleton instances and factory functions for maximum flexibility.
Core Components¶
ServiceContainer¶
ServiceContainer
¶
A lightweight dependency injection container that supports both singleton instances and factory functions for creating services.
Source code in src/starstreamer/core/di/container.py
clear
¶
get_registered_types
¶
Get all registered service types.
Returns:
| Type | Description |
|---|---|
set[type]
|
Set of all registered service types |
is_registered
¶
Check if a service type is registered.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service_type
|
type
|
The type to check |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the service is registered, False otherwise |
Source code in src/starstreamer/core/di/container.py
register_factory
¶
Register a factory function for creating service instances.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service_type
|
type[T]
|
The type/interface that this service implements |
required |
factory
|
Callable[[], T]
|
A callable that returns a new service instance |
required |
Example
container.register_factory(Logger, lambda: logging.getLogger("starstreamer"))
Source code in src/starstreamer/core/di/container.py
register_singleton
¶
Register a singleton service instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service_type
|
type[T]
|
The type/interface that this service implements |
required |
instance
|
T
|
The actual service instance |
required |
Example
container.register_singleton(TwitchClient, twitch_client_instance)
Source code in src/starstreamer/core/di/container.py
resolve
¶
Resolve a service by its type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service_type
|
type[T]
|
The type of service to resolve |
required |
Returns:
| Type | Description |
|---|---|
T
|
The service instance |
Raises:
| Type | Description |
|---|---|
KeyError
|
If the service type is not registered |
Example
twitch_client = container.resolve(TwitchClient)
Source code in src/starstreamer/core/di/container.py
The ServiceContainer is the heart of the dependency injection system, managing service registration and resolution.
Constructor¶
Creates a new container with no registered services.
Service Registration¶
register_singleton(service_type: type[T], instance: T) -> None¶
Register a singleton service instance that will be reused for all requests.
Parameters:
- service_type: The type/interface that this service implements
- instance: The actual service instance
Usage:
from starstreamer.core.di.container import ServiceContainer
from starstreamer.plugins.twitch import TwitchClient
from starstreamer.plugins.obs import OBSClient
from starstreamer.plugins.elevenlabs import ElevenLabsClient
import logging
container = ServiceContainer()
# Register singleton services
twitch_client = TwitchClient() # Uses config.yaml settings
container.register_singleton(TwitchClient, twitch_client)
obs_client = OBSClient()
container.register_singleton(OBSClient, obs_client)
elevenlabs_client = ElevenLabsClient()
container.register_singleton(ElevenLabsClient, elevenlabs_client)
# Register other services
logger = logging.getLogger("starstreamer")
container.register_singleton(logging.Logger, logger)
register_factory(service_type: type[T], factory: Callable[[], T]) -> None¶
Register a factory function that creates new service instances on each request.
Parameters:
- service_type: The type/interface that this service implements
- factory: A callable that returns a new service instance
Usage:
# Register factory for per-request instances
container.register_factory(
DatabaseConnection,
lambda: DatabaseConnection("sqlite:///data.db")
)
# Register with configuration
def create_logger():
logger = logging.getLogger(f"handler-{uuid4()}")
logger.setLevel(logging.INFO)
return logger
container.register_factory(logging.Logger, create_logger)
Service Resolution¶
resolve(service_type: type[T]) -> T¶
Resolve a service by its type.
Parameters:
- service_type: The type of service to resolve
Returns: The service instance
Raises: KeyError if the service type is not registered
Usage:
# Resolve services
twitch = container.resolve(TwitchClient)
logger = container.resolve(logging.Logger)
# Use in event handlers (done automatically)
async def my_handler(event: Event, twitch: TwitchClient, logger: logging.Logger):
await twitch.send_message("Hello!")
logger.info("Message sent")
Container Management¶
is_registered(service_type: type) -> bool¶
Check if a service type is registered.
if container.is_registered(TwitchClient):
twitch = container.resolve(TwitchClient)
else:
print("TwitchClient not registered")
get_registered_types() -> set[type]¶
Get all registered service types.
registered_types = container.get_registered_types()
print(f"Registered services: {[t.__name__ for t in registered_types]}")
clear() -> None¶
Clear all registered services.
HandlerRegistry¶
HandlerRegistry
¶
Registry that manages handler registration and dependency injection.
Supports both legacy (event, ctx) and explicit dependency injection styles.
Source code in src/starstreamer/core/di/registry.py
clear_handlers
¶
Clear handlers for a specific event type or all event types.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_type
|
str | None
|
Event type to clear, or None to clear all |
None
|
Source code in src/starstreamer/core/di/registry.py
create_active_trigger_wrapper
¶
Create a wrapper for active trigger handlers with dependency injection.
This allows active triggers to use the same DI system as event handlers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
Callable[..., Any]
|
The handler function to wrap |
required |
Returns:
| Type | Description |
|---|---|
Callable[[Any, Any], Awaitable[None]]
|
Wrapped handler compatible with active trigger execution |
Source code in src/starstreamer/core/di/registry.py
get_handlers
¶
Get all registered handlers for an event type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_type
|
str
|
The event type to get handlers for |
required |
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
List of handler records sorted by priority |
Source code in src/starstreamer/core/di/registry.py
get_stats
¶
Get statistics about registered handlers.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary with handler statistics |
Source code in src/starstreamer/core/di/registry.py
register
¶
register(event_type: str, handler: Callable[..., Any], filters: list[Callable[..., bool]] | None = None, priority: int = 5) -> None
Register a handler for an event type with automatic dependency injection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_type
|
str
|
The event type to handle (e.g., "twitch.chat.message") |
required |
handler
|
Callable[..., Any]
|
The handler function (legacy or explicit style) |
required |
filters
|
list[Callable[..., bool]] | None
|
Optional filter functions |
None
|
priority
|
int
|
Handler priority (lower = higher priority) |
5
|
Source code in src/starstreamer/core/di/registry.py
unregister
¶
Unregister a handler for an event type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_type
|
str
|
The event type |
required |
handler
|
Callable[..., Any]
|
The original handler function |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if handler was found and removed, False otherwise |
Source code in src/starstreamer/core/di/registry.py
unregister_module_handlers
¶
Unregister all handlers from a specific module.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module_name
|
str
|
The module name (e.g., "chat", "alerts") |
required |
Returns:
| Type | Description |
|---|---|
int
|
Number of handlers removed |
Source code in src/starstreamer/core/di/registry.py
validate_dependencies
¶
Validate that all handler dependencies can be resolved.
Returns:
| Type | Description |
|---|---|
list[str]
|
List of error messages for missing dependencies |
Source code in src/starstreamer/core/di/registry.py
The HandlerRegistry manages event handler registration and creates dependency injection wrappers.
Constructor¶
def __init__(self, container: ServiceContainer) -> None:
"""Initialize registry with a service container"""
Handler Registration¶
register(event_type: str, handler: Callable, filters: list = None, priority: int = 5) -> None¶
Register a handler with automatic dependency injection.
Parameters:
- event_type: The event type to handle (e.g., "twitch.chat.message")
- handler: The handler function (legacy or explicit style)
- filters: Optional filter functions
- priority: Handler priority (lower = higher priority)
Handler Styles:
Legacy Style (maintained for compatibility):
async def legacy_handler(event, ctx):
"""Old style handler with context object"""
await ctx.twitch.send_message("Hello from legacy handler!")
Explicit Style (recommended):
async def explicit_handler(event: Event, twitch: TwitchClient, logger: logging.Logger):
"""New style with explicit dependency injection"""
user = event.data.get("user", {})
username = user.get("display_name", "Unknown")
await twitch.send_message(f"Hello {username}!")
logger.info(f"Greeted {username}")
Handler Management¶
get_handlers(event_type: str) -> list[dict]¶
Get all registered handlers for an event type.
handlers = registry.get_handlers("twitch.chat.message")
for handler_info in handlers:
print(f"Handler: {handler_info['handler_name']}")
print(f"Style: {handler_info['style']}")
print(f"Dependencies: {handler_info['dependencies']}")
unregister(event_type: str, handler: Callable) -> bool¶
Unregister a handler.
success = registry.unregister("twitch.chat.message", my_handler)
if success:
print("Handler unregistered")
clear_handlers(event_type: str = None) -> None¶
Clear handlers for a specific event type or all event types.
registry.clear_handlers("twitch.chat.message") # Clear specific event
registry.clear_handlers() # Clear all handlers
Statistics and Validation¶
get_stats() -> dict¶
Get statistics about registered handlers.
stats = registry.get_stats()
print(f"Total handlers: {stats['total_handlers']}")
print(f"Event types: {stats['event_types']}")
print(f"Style distribution: {stats['style_distribution']}")
print(f"Registered services: {stats['registered_services']}")
validate_dependencies() -> list[str]¶
Validate that all handler dependencies can be resolved.
errors = registry.validate_dependencies()
if errors:
for error in errors:
print(f"Dependency error: {error}")
HandlerInspector¶
HandlerInspector
¶
Utility class for inspecting handler function signatures to determine their style and extract dependency information.
get_dependencies
staticmethod
¶
Extract dependency types from an explicit-style handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
Callable[..., Any]
|
The handler function to inspect |
required |
Returns:
| Type | Description |
|---|---|
list[type]
|
List of parameter types (excluding the first 'event' parameter) |
Example
async def handler(event: Event, twitch: TwitchClient, logger: Logger): pass get_dependencies(handler) -> [TwitchClient, Logger]
Source code in src/starstreamer/core/di/inspector.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | |
get_parameter_names
staticmethod
¶
Get the parameter names of a handler function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
Callable[..., Any]
|
The handler function to inspect |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
List of parameter names |
Example
async def handler(event, twitch, logger): pass get_parameter_names(handler) -> ['event', 'twitch', 'logger']
Source code in src/starstreamer/core/di/inspector.py
get_style
staticmethod
¶
Determine the style of a handler function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
Callable[..., Any]
|
The handler function to inspect |
required |
Returns:
| Type | Description |
|---|---|
HandlerStyle
|
HandlerStyle.LEGACY if it's (event, ctx) style |
HandlerStyle
|
HandlerStyle.EXPLICIT if it's explicit dependency style |
Example
Legacy style¶
async def old_handler(event, ctx): ... get_style(old_handler) -> HandlerStyle.LEGACY
Explicit style¶
async def new_handler(event, twitch: TwitchClient): ... get_style(new_handler) -> HandlerStyle.EXPLICIT
Source code in src/starstreamer/core/di/inspector.py
inspect_handler
classmethod
¶
Perform a complete inspection of a handler function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
Callable[..., Any]
|
The handler function to inspect |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary containing all inspection results |
Example
info = HandlerInspector.inspect_handler(my_handler) print(f"Style: {info['style']}") print(f"Dependencies: {info['dependencies']}")
Source code in src/starstreamer/core/di/inspector.py
is_async_handler
staticmethod
¶
Check if a handler is an async function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
Callable[..., Any]
|
The handler function to inspect |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the handler is async, False otherwise |
Source code in src/starstreamer/core/di/inspector.py
validate_handler
staticmethod
¶
Validate that a handler has a valid signature.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
Callable[..., Any]
|
The handler function to validate |
required |
Returns:
| Type | Description |
|---|---|
tuple[bool, str]
|
Tuple of (is_valid, error_message) |
Example
is_valid, error = validate_handler(my_handler) if not is_valid: print(f"Invalid handler: {error}")
Source code in src/starstreamer/core/di/inspector.py
The HandlerInspector analyzes handler function signatures to determine their style and dependencies.
Handler Style Detection¶
get_style(handler: Callable) -> HandlerStyle¶
Determine if a handler uses legacy or explicit style.
from starstreamer.core.di.inspector import HandlerInspector, HandlerStyle
async def legacy_handler(event, ctx): pass
async def explicit_handler(event: Event, twitch: TwitchClient): pass
assert HandlerInspector.get_style(legacy_handler) == HandlerStyle.LEGACY
assert HandlerInspector.get_style(explicit_handler) == HandlerStyle.EXPLICIT
Dependency Analysis¶
get_dependencies(handler: Callable) -> list[type]¶
Extract dependency types from explicit-style handlers.
async def handler(event: Event, twitch: TwitchClient, logger: logging.Logger):
pass
dependencies = HandlerInspector.get_dependencies(handler)
# Returns: [TwitchClient, logging.Logger]
Advanced Type Handling:
from typing import Optional
async def advanced_handler(
event: Event,
twitch: TwitchClient,
optional_service: Optional[MyService], # Handles Optional types
logger: logging.Logger
):
pass
dependencies = HandlerInspector.get_dependencies(advanced_handler)
# Returns: [TwitchClient, MyService, logging.Logger]
Handler Validation¶
validate_handler(handler: Callable) -> tuple[bool, str]¶
Validate that a handler has a correct signature.
is_valid, error_message = HandlerInspector.validate_handler(my_handler)
if not is_valid:
print(f"Invalid handler: {error_message}")
inspect_handler(handler: Callable) -> dict¶
Perform complete handler inspection.
info = HandlerInspector.inspect_handler(my_handler)
print(f"Style: {info['style']}")
print(f"Dependencies: {info['dependencies']}")
print(f"Parameter names: {info['parameter_names']}")
print(f"Is async: {info['is_async']}")
print(f"Is valid: {info['is_valid']}")
Setup Functions¶
StarStreamer provides helper functions for easy dependency injection setup.
setup_dependency_injection() -> tuple[ServiceContainer, HandlerRegistry]¶
setup_dependency_injection
¶
Set up dependency injection for the event system.
Returns:
| Type | Description |
|---|---|
tuple[Any, Any]
|
Tuple of (ServiceContainer, HandlerRegistry) configured for DI |
Example
container, registry = setup_dependency_injection()
Register services¶
container.register_singleton(TwitchClient, twitch_client) container.register_singleton(Logger, logger)
Set up EventBus with DI¶
event_bus = get_event_bus() event_bus.set_handler_registry(registry)
Source code in src/starstreamer/core/decorators.py
Set up a complete dependency injection system.
from starstreamer.core.decorators import setup_dependency_injection
# Create DI system
container, registry = setup_dependency_injection()
# Register services
container.register_singleton(TwitchClient, twitch_client)
container.register_singleton(logging.Logger, logger)
# Use with event bus
from starstreamer.core.event_bus import EventBus
event_bus = EventBus(registry)
configure_event_bus_with_di(container: ServiceContainer, registry: HandlerRegistry) -> None¶
configure_event_bus_with_di
¶
Configure the global event bus to use dependency injection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
_container
|
Any
|
ServiceContainer instance (unused, kept for API compatibility) |
required |
registry
|
Any
|
HandlerRegistry instance |
required |
This updates the global event bus to use the dependency injection system for all future handler registrations.
Source code in src/starstreamer/core/decorators.py
Configure the global event bus to use dependency injection.
from starstreamer.core.decorators import (
setup_dependency_injection,
configure_event_bus_with_di
)
container, registry = setup_dependency_injection()
configure_event_bus_with_di(container, registry)
# Now all @on_event handlers will use DI automatically
register_common_services(container: ServiceContainer) -> None¶
register_common_services
¶
Register commonly used services in the container.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
container
|
Any
|
ServiceContainer to register services in |
required |
This is a convenience function that registers standard services that most handlers will need. Call this after setting up DI.
Source code in src/starstreamer/core/decorators.py
Register commonly used services.
from starstreamer.core.decorators import register_common_services
container, registry = setup_dependency_injection()
register_common_services(container) # Registers default logger
# Add your platform services
container.register_singleton(TwitchClient, twitch_client)
create_di_event_bus() -> EventBus¶
create_di_event_bus
¶
Create a new EventBus instance with dependency injection configured.
Returns:
| Type | Description |
|---|---|
Any
|
EventBus instance with DI support enabled |
Example
Create DI-enabled event bus¶
event_bus = create_di_event_bus()
Get the container to register services¶
container = event_bus.handler_registry.container container.register_singleton(MyService, service_instance)
Now handlers can use explicit dependency injection¶
Source code in src/starstreamer/core/decorators.py
Create an EventBus with dependency injection pre-configured.
from starstreamer.core.decorators import create_di_event_bus
# Get fully configured event bus
event_bus = create_di_event_bus()
# Access container to register services
container = event_bus.handler_registry.container
container.register_singleton(TwitchClient, twitch_client)
Complete Setup Example¶
Here's a complete example of setting up dependency injection in a StarStreamer application:
Main Application Setup¶
import asyncio
import logging
from starstreamer.core.decorators import setup_dependency_injection, configure_event_bus_with_di
from starstreamer.plugins.twitch import TwitchClient
from starstreamer.services.economy import EconomyService
from starstreamer.services.users import UserService
async def main():
# Setup dependency injection
container, registry = setup_dependency_injection()
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("starstreamer")
# Create and register services
twitch_client = TwitchClient() # Uses config.yaml and database settings
economy_service = EconomyService()
user_service = UserService()
# Register services in container
container.register_singleton(TwitchClient, twitch_client)
container.register_singleton(EconomyService, economy_service)
container.register_singleton(UserService, user_service)
container.register_singleton(logging.Logger, logger)
# Configure global event bus
configure_event_bus_with_di(container, registry)
# Validate all dependencies
errors = registry.validate_dependencies()
if errors:
for error in errors:
logger.error(f"Dependency error: {error}")
return
# Import modules to register handlers
import modules.chat.actions.basic_commands
import modules.rpg.actions.economy_commands
# Start the application
await twitch_client.connect()
logger.info("StarStreamer started with dependency injection")
if __name__ == "__main__":
asyncio.run(main())
Service Implementation¶
from starstreamer.core.di.container import ServiceContainer
class EconomyService:
"""Business logic service for economy features"""
def __init__(self, db_connection=None):
self.db = db_connection or get_default_db()
async def get_balance(self, user_id: str) -> int:
"""Get user's currency balance"""
# Database logic
return await self.db.fetchval(
"SELECT balance FROM users WHERE user_id = ?",
(user_id,)
) or 0
async def add_currency(self, user_id: str, amount: int) -> int:
"""Add currency to user's balance"""
# Transaction logic
pass
# Register with factory for database connection
def create_economy_service():
db = DatabaseConnection("sqlite:///economy.db")
return EconomyService(db)
container.register_factory(EconomyService, create_economy_service)
Handler Implementation¶
from starstreamer import on_event
from starstreamer.triggers import trigger, CommandTrigger, CooldownTrigger
from starstreamer.plugins.twitch import TwitchClient
from starstreamer.plugins.elevenlabs import ElevenLabsClient
from starstreamer.services.economy import EconomyService
from starstreamer.runtime.types import Event
import logging
@on_event("twitch.chat.message")
@trigger(CommandTrigger("!balance"))
async def balance_command(
event: Event,
twitch: TwitchClient, # Injected service
economy: EconomyService, # Injected service
logger: logging.Logger # Injected service
) -> None:
"""Check user's currency balance"""
user = event.data.get("user", {})
user_id = user.get("id", "")
username = user.get("display_name", "Unknown")
if not user_id:
await twitch.send_message(f"@{username} Unable to get your user ID")
return
# Use injected services
balance = await economy.get_balance(user_id)
await twitch.send_message(f"@{username} Your balance: {balance} coins")
logger.info(f"Balance check: {username} has {balance} coins")
@on_event("twitch.chat.message")
@trigger(CommandTrigger("!tts"))
async def tts_command(
event: Event,
elevenlabs: ElevenLabsClient, # ElevenLabs TTS service
twitch: TwitchClient, # Twitch messaging service
logger: logging.Logger # Logging service
) -> None:
"""Text-to-speech command with multiple service injection"""
message = event.data.get("message", "")
parts = message.split(maxsplit=1)
user = event.data.get("user", {})
username = user.get("display_name", "Someone")
if len(parts) < 2:
await twitch.send_message(f"@{username} Usage: !tts <text to speak>")
return
text_to_speak = parts[1].strip()
# Use injected ElevenLabs service
voices = await elevenlabs.get_voices_as_objects()
if not voices:
await twitch.send_message(f"@{username} No voices available")
return
# Generate speech with first available voice
audio_bytes = await elevenlabs.text_to_speech(text_to_speak, voice=voices[0])
# Use injected Twitch and logging services
await twitch.send_message(f"🔊 Generated TTS for {username}")
logger.info(f"TTS generated: {text_to_speak[:50]}...")
@on_event("twitch.chat.message")
@trigger(CommandTrigger("!work"))
@trigger(CooldownTrigger(300, per_user=True)) # 5 minute cooldown
async def work_command(
event: Event,
twitch: TwitchClient,
economy: EconomyService,
logger: logging.Logger
) -> None:
"""Work to earn currency with dependency injection"""
user = event.data.get("user", {})
user_id = user.get("id", "")
username = user.get("display_name", "Unknown")
# Generate work reward
import random
earned = random.randint(50, 150)
# Use injected economy service
new_balance = await economy.add_currency(user_id, earned)
await twitch.send_message(
f"@{username} You worked hard and earned {earned} coins! "
f"Your new balance: {new_balance} coins"
)
logger.info(f"Work command: {username} earned {earned} coins")
Advanced Patterns¶
Custom Service Factory¶
class ConfigurableService:
def __init__(self, config: dict):
self.config = config
def get_setting(self, key: str):
return self.config.get(key)
# Register with custom factory
def create_configurable_service():
config = load_config_from_file("settings.json")
return ConfigurableService(config)
container.register_factory(ConfigurableService, create_configurable_service)
Service Composition¶
class CompositeService:
def __init__(self, twitch: TwitchClient, economy: EconomyService):
self.twitch = twitch
self.economy = economy
async def reward_user(self, user_id: str, amount: int):
"""Composite operation using multiple services"""
new_balance = await self.economy.add_currency(user_id, amount)
await self.twitch.send_message(f"Rewarded! New balance: {new_balance}")
# Register composite service (resolved dependencies automatically)
def create_composite_service():
twitch = container.resolve(TwitchClient)
economy = container.resolve(EconomyService)
return CompositeService(twitch, economy)
container.register_factory(CompositeService, create_composite_service)
Testing with Dependency Injection¶
import pytest
from unittest.mock import Mock, AsyncMock
@pytest.fixture
def test_container():
"""Create container with mock services for testing"""
container = ServiceContainer()
# Mock services
mock_twitch = Mock(spec=TwitchClient)
mock_twitch.send_message = AsyncMock()
mock_economy = Mock(spec=EconomyService)
mock_economy.get_balance = AsyncMock(return_value=100)
# Register mocks
container.register_singleton(TwitchClient, mock_twitch)
container.register_singleton(EconomyService, mock_economy)
return container
@pytest.mark.asyncio
async def test_balance_command(test_container):
"""Test balance command with mocked dependencies"""
from modules.rpg.actions.economy_commands import balance_command
# Create test event
event = Event(
type="twitch.chat.message",
data={"user": {"id": "123", "display_name": "TestUser"}},
timestamp=time.time(),
source="test"
)
# Resolve mocked services
twitch = test_container.resolve(TwitchClient)
economy = test_container.resolve(EconomyService)
logger = Mock()
# Call handler with injected dependencies
await balance_command(event, twitch, economy, logger)
# Verify interactions
economy.get_balance.assert_called_once_with("123")
twitch.send_message.assert_called_once_with("@TestUser Your balance: 100 coins")