Skip to main content

ferro_rs/container/
testing.rs

1//! Test utilities for the Application Container
2//!
3//! Provides mechanisms for test isolation via thread-local container overrides.
4//!
5//! # Example
6//!
7//! ```rust,ignore
8//! use ferro_rs::testing::{TestContainer, TestContainerGuard};
9//!
10//! #[tokio::test]
11//! async fn test_with_fake_service() {
12//!     // Set up test container - automatically cleared when guard is dropped
13//!     let _guard = TestContainer::fake();
14//!
15//!     // Register fake implementations
16//!     TestContainer::bind::<dyn HttpClient>(Arc::new(FakeHttpClient::new()));
17//!
18//!     // App::make() will now return the fake
19//!     let client: Arc<dyn HttpClient> = App::make::<dyn HttpClient>().unwrap();
20//! }
21//! ```
22
23use super::{Container, TEST_CONTAINER};
24use std::any::Any;
25use std::sync::Arc;
26
27/// Test utilities for the container
28///
29/// Provides methods to set up isolated test containers with fake implementations.
30/// Test containers use thread-local storage, so tests run in parallel won't interfere.
31pub struct TestContainer;
32
33impl TestContainer {
34    /// Set up a test container with overrides
35    ///
36    /// Returns a guard that clears the test container when dropped.
37    /// This ensures test isolation - each test gets a fresh container.
38    ///
39    /// # Example
40    /// ```rust,ignore
41    /// #[tokio::test]
42    /// async fn my_test() {
43    ///     let _guard = TestContainer::fake();
44    ///     // Register fakes...
45    /// } // Container automatically cleared here
46    /// ```
47    pub fn fake() -> TestContainerGuard {
48        TEST_CONTAINER.with(|c| {
49            *c.borrow_mut() = Some(Container::new());
50        });
51        TestContainerGuard
52    }
53
54    /// Register a fake singleton for testing
55    ///
56    /// # Example
57    /// ```rust,ignore
58    /// TestContainer::singleton(FakeDatabase::new());
59    /// ```
60    pub fn singleton<T: Any + Send + Sync + 'static>(instance: T) {
61        TEST_CONTAINER.with(|c| {
62            if let Some(ref mut container) = *c.borrow_mut() {
63                container.singleton(instance);
64            }
65        });
66    }
67
68    /// Register a fake factory for testing
69    ///
70    /// # Example
71    /// ```rust,ignore
72    /// TestContainer::factory(|| FakeLogger::new());
73    /// ```
74    pub fn factory<T, F>(factory: F)
75    where
76        T: Any + Send + Sync + 'static,
77        F: Fn() -> T + Send + Sync + 'static,
78    {
79        TEST_CONTAINER.with(|c| {
80            if let Some(ref mut container) = *c.borrow_mut() {
81                container.factory(factory);
82            }
83        });
84    }
85
86    /// Bind a fake trait implementation for testing
87    ///
88    /// # Example
89    /// ```rust,ignore
90    /// TestContainer::bind::<dyn HttpClient>(Arc::new(FakeHttpClient::new()));
91    /// ```
92    pub fn bind<T: ?Sized + Send + Sync + 'static>(instance: Arc<T>) {
93        TEST_CONTAINER.with(|c| {
94            if let Some(ref mut container) = *c.borrow_mut() {
95                container.bind(instance);
96            }
97        });
98    }
99
100    /// Bind a fake trait factory for testing
101    ///
102    /// # Example
103    /// ```rust,ignore
104    /// TestContainer::bind_factory::<dyn HttpClient>(|| Arc::new(FakeHttpClient::new()));
105    /// ```
106    pub fn bind_factory<T: ?Sized + Send + Sync + 'static, F>(factory: F)
107    where
108        F: Fn() -> Arc<T> + Send + Sync + 'static,
109    {
110        TEST_CONTAINER.with(|c| {
111            if let Some(ref mut container) = *c.borrow_mut() {
112                container.bind_factory(factory);
113            }
114        });
115    }
116}
117
118/// Guard that clears the test container when dropped
119///
120/// This ensures test isolation by automatically cleaning up the thread-local
121/// test container when the guard goes out of scope.
122pub struct TestContainerGuard;
123
124impl Drop for TestContainerGuard {
125    fn drop(&mut self) {
126        TEST_CONTAINER.with(|c| {
127            *c.borrow_mut() = None;
128        });
129    }
130}