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}