test: Standardize service mocking with shared pytest fixtures for reliable test isolation (#11350)

This commit is contained in:
Cristhian Zanforlin Lousa
2026-01-19 08:08:38 -03:00
committed by GitHub
parent 2f274012e1
commit d62a9a6720
5 changed files with 113 additions and 27 deletions

View File

@ -0,0 +1,54 @@
"""Shared fixtures for service tests."""
from unittest.mock import MagicMock
import pytest
from lfx.services.base import Service
from lfx.services.manager import ServiceManager
from lfx.services.schema import ServiceType
class MockSessionService(Service):
"""Mock session service for testing.
Provides a minimal session service implementation that satisfies
the dependency requirements of services like LocalStorageService.
"""
name = "session_service"
def __init__(self):
super().__init__()
self.set_ready()
async def teardown(self) -> None:
pass
@pytest.fixture
def mock_session_service():
"""Create a mock session service using MagicMock."""
return MagicMock()
@pytest.fixture
def mock_settings_service(tmp_path):
"""Create a mock settings service with tmp_path as config_dir."""
mock = MagicMock()
mock.settings.config_dir = str(tmp_path)
return mock
@pytest.fixture
def service_manager_with_session():
"""Create a ServiceManager with MockSessionService registered.
This fixture is useful for tests that need to create services
via the ServiceManager that depend on session_service.
"""
import asyncio
manager = ServiceManager()
manager.register_service_class(ServiceType.SESSION_SERVICE, MockSessionService, override=True)
yield manager
asyncio.run(manager.teardown())

View File

@ -1,5 +1,7 @@
"""Tests for decorator-based service registration."""
from unittest.mock import MagicMock
import pytest
from lfx.services.base import Service
from lfx.services.manager import ServiceManager
@ -8,15 +10,17 @@ from lfx.services.storage.local import LocalStorageService
from lfx.services.telemetry.service import TelemetryService
from lfx.services.tracing.service import TracingService
from .conftest import MockSessionService
@pytest.fixture
def clean_manager():
"""Create a fresh ServiceManager for testing decorators."""
manager = ServiceManager()
yield manager
# Cleanup
import asyncio
manager = ServiceManager()
manager.register_service_class(ServiceType.SESSION_SERVICE, MockSessionService, override=True)
yield manager
asyncio.run(manager.teardown())
@ -111,7 +115,11 @@ class TestDecoratorRegistration:
clean_manager.register_service_class(ServiceType.VARIABLE_SERVICE, LocalStorageService, override=True)
# Class should still be usable directly (not just through manager)
direct_instance = LocalStorageService()
# Create mock dependencies for direct instantiation
mock_session = MagicMock()
mock_settings = MagicMock()
mock_settings.settings.config_dir = "/tmp/test"
direct_instance = LocalStorageService(mock_session, mock_settings)
assert direct_instance.ready is True
assert direct_instance.name == "storage_service"
@ -121,8 +129,8 @@ class TestDecoratorRegistration:
clean_manager.register_service_class(ServiceType.TELEMETRY_SERVICE, TelemetryService, override=True)
clean_manager.register_service_class(ServiceType.TRACING_SERVICE, TracingService, override=True)
# All should be registered
assert len(clean_manager.service_classes) == 3
# All should be registered (plus SESSION_SERVICE from fixture)
assert len(clean_manager.service_classes) >= 3
assert ServiceType.STORAGE_SERVICE in clean_manager.service_classes
assert ServiceType.TELEMETRY_SERVICE in clean_manager.service_classes
assert ServiceType.TRACING_SERVICE in clean_manager.service_classes

View File

@ -7,6 +7,8 @@ from lfx.services.base import Service
from lfx.services.manager import ServiceManager
from lfx.services.schema import ServiceType
from .conftest import MockSessionService
class TestStandaloneLFX:
"""Test LFX running standalone without langflow."""
@ -15,6 +17,8 @@ class TestStandaloneLFX:
def clean_manager(self):
"""Create a clean ServiceManager instance."""
manager = ServiceManager()
# Register mock session service as dependency for LocalStorageService
manager.register_service_class(ServiceType.SESSION_SERVICE, MockSessionService, override=True)
yield manager
# Cleanup
import asyncio
@ -92,6 +96,8 @@ cache_service = "lfx.services.cache.service:ThreadingInMemoryCache"
def clean_manager(self):
"""Create a clean ServiceManager instance."""
manager = ServiceManager()
# Register mock session service as dependency for LocalStorageService
manager.register_service_class(ServiceType.SESSION_SERVICE, MockSessionService, override=True)
yield manager
# Cleanup
import asyncio
@ -126,6 +132,8 @@ class TestServiceOverrideScenarios:
def clean_manager(self):
"""Create a clean ServiceManager instance."""
manager = ServiceManager()
# Register mock session service as dependency for LocalStorageService
manager.register_service_class(ServiceType.SESSION_SERVICE, MockSessionService, override=True)
yield manager
# Cleanup
import asyncio
@ -194,6 +202,8 @@ class TestErrorConditions:
def clean_manager(self):
"""Create a clean ServiceManager instance."""
manager = ServiceManager()
# Register mock session service as dependency for LocalStorageService
manager.register_service_class(ServiceType.SESSION_SERVICE, MockSessionService, override=True)
yield manager
# Cleanup
import asyncio
@ -233,8 +243,9 @@ invalid_service_type = "some.module:SomeClass"
# Should not raise, just log warning
clean_manager.discover_plugins(config_dir)
# No services should be registered (invalid key)
assert len(clean_manager.service_classes) == 0
# No services should be registered from config (only SESSION_SERVICE from fixture)
assert len(clean_manager.service_classes) == 1
assert ServiceType.SESSION_SERVICE in clean_manager.service_classes
def test_malformed_toml_in_config(self, clean_manager, tmp_path):
"""Test handling of malformed TOML."""
@ -251,8 +262,9 @@ storage_service = "lfx.services.storage.local:LocalStorageService"
# Should not raise, just log warning
clean_manager.discover_plugins(config_dir)
# No services should be registered
assert len(clean_manager.service_classes) == 0
# No services should be registered from config (only SESSION_SERVICE from fixture)
assert len(clean_manager.service_classes) == 1
assert ServiceType.SESSION_SERVICE in clean_manager.service_classes
def test_service_without_name_attribute(self, clean_manager):
"""Test registering a service without name attribute."""
@ -295,6 +307,8 @@ class TestDependencyResolution:
def clean_manager(self):
"""Create a clean ServiceManager instance."""
manager = ServiceManager()
# Register mock session service as dependency for LocalStorageService
manager.register_service_class(ServiceType.SESSION_SERVICE, MockSessionService, override=True)
yield manager
# Cleanup
import asyncio
@ -390,6 +404,8 @@ class TestConfigFileDiscovery:
def clean_manager(self):
"""Create a clean ServiceManager instance."""
manager = ServiceManager()
# Register mock session service as dependency for LocalStorageService
manager.register_service_class(ServiceType.SESSION_SERVICE, MockSessionService, override=True)
yield manager
# Cleanup
import asyncio
@ -446,7 +462,9 @@ cache_service = "lfx.services.cache.service:ThreadingInMemoryCache"
# Should not raise
clean_manager.discover_plugins(config_dir)
assert len(clean_manager.service_classes) == 0
# No services should be registered from config (only SESSION_SERVICE from fixture)
assert len(clean_manager.service_classes) == 1
assert ServiceType.SESSION_SERVICE in clean_manager.service_classes
class TestEnvironmentVariableIntegration:
@ -456,6 +474,8 @@ class TestEnvironmentVariableIntegration:
def clean_manager(self):
"""Create a clean ServiceManager instance."""
manager = ServiceManager()
# Register mock session service as dependency for LocalStorageService
manager.register_service_class(ServiceType.SESSION_SERVICE, MockSessionService, override=True)
yield manager
# Cleanup
import asyncio

View File

@ -13,9 +13,9 @@ class TestLocalStorageService:
"""Tests for LocalStorageService."""
@pytest.fixture
def storage(self, tmp_path):
def storage(self, mock_session_service, mock_settings_service):
"""Create a storage service with temp directory."""
return LocalStorageService(data_dir=tmp_path)
return LocalStorageService(mock_session_service, mock_settings_service)
@pytest.mark.asyncio
async def test_save_and_get_file(self, storage):
@ -61,10 +61,11 @@ class TestLocalStorageService:
with pytest.raises(FileNotFoundError):
await storage.get_file("flow_123", "nonexistent.txt")
def test_build_full_path(self, storage, tmp_path):
def test_build_full_path(self, storage, mock_settings_service):
"""Test building full file path."""
path = storage.build_full_path("flow_123", "test.txt")
expected = str(tmp_path / "flow_123" / "test.txt")
config_dir = mock_settings_service.settings.config_dir
expected = f"{config_dir}/flow_123/test.txt"
assert path == expected
@pytest.mark.asyncio
@ -216,9 +217,9 @@ class TestMinimalServicesIntegration:
"""Integration tests for minimal services working together."""
@pytest.mark.asyncio
async def test_all_minimal_services_initialize(self, tmp_path):
async def test_all_minimal_services_initialize(self, mock_session_service, mock_settings_service):
"""Test that all minimal services can be initialized."""
storage = LocalStorageService(data_dir=tmp_path)
storage = LocalStorageService(mock_session_service, mock_settings_service)
telemetry = TelemetryService()
tracing = TracingService()
variables = VariableService()
@ -229,9 +230,9 @@ class TestMinimalServicesIntegration:
assert variables.ready
@pytest.mark.asyncio
async def test_minimal_services_teardown_all(self, tmp_path):
async def test_minimal_services_teardown_all(self, mock_session_service, mock_settings_service):
"""Test tearing down all minimal services."""
storage = LocalStorageService(data_dir=tmp_path)
storage = LocalStorageService(mock_session_service, mock_settings_service)
telemetry = TelemetryService()
tracing = TracingService()
variables = VariableService()
@ -243,9 +244,9 @@ class TestMinimalServicesIntegration:
await variables.teardown()
@pytest.mark.asyncio
async def test_storage_with_tracing(self, tmp_path):
async def test_storage_with_tracing(self, mock_session_service, mock_settings_service):
"""Test using storage with tracing."""
storage = LocalStorageService(data_dir=tmp_path)
storage = LocalStorageService(mock_session_service, mock_settings_service)
tracing = TracingService()
tracing.add_log("storage_test", {"operation": "save", "flow_id": "123"})

View File

@ -11,15 +11,17 @@ from lfx.services.telemetry.service import TelemetryService
from lfx.services.tracing.service import TracingService
from lfx.services.variable.service import VariableService
from .conftest import MockSessionService
@pytest.fixture
def service_manager():
"""Create a fresh ServiceManager for each test."""
manager = ServiceManager()
yield manager
# Cleanup
import asyncio
manager = ServiceManager()
manager.register_service_class(ServiceType.SESSION_SERVICE, MockSessionService, override=True)
yield manager
asyncio.run(manager.teardown())
@ -48,7 +50,8 @@ class TestServiceRegistration:
service_manager.register_service_class(ServiceType.TRACING_SERVICE, TracingService, override=True)
service_manager.register_service_class(ServiceType.VARIABLE_SERVICE, VariableService, override=True)
assert len(service_manager.service_classes) == 4
# 4 services + SESSION_SERVICE from fixture = 5
assert len(service_manager.service_classes) == 5
assert service_manager.service_classes[ServiceType.STORAGE_SERVICE] == LocalStorageService
assert service_manager.service_classes[ServiceType.TELEMETRY_SERVICE] == TelemetryService
assert service_manager.service_classes[ServiceType.TRACING_SERVICE] == TracingService
@ -464,8 +467,8 @@ variable_service = "lfx.services.variable.service:VariableService"
service_manager.discover_plugins(temp_config_dir)
# All services should be registered
assert len(service_manager.service_classes) == 4
# All services should be registered (4 from config + SESSION_SERVICE from fixture = 5)
assert len(service_manager.service_classes) == 5
# Create and verify each service
storage = service_manager.get(ServiceType.STORAGE_SERVICE)