actor_framework/
mock.rs

1//! # Mock Framework & Testing Guide
2//!
3//! The `MockClient<T>` type implements the same `ResourceClient<T>` API as the production client but operates entirely in‑memory. It lets you set expectations and return values for unit tests, enabling fast, deterministic testing of client logic without spawning any actors.
4//!
5//! ## When to use Mocks vs Real Actors
6//!
7//! | Feature | MockClient | Real Actor |
8//! |---------|------------|------------|
9//! | **Speed** | Instant (in-memory) | Fast (but involves tokio spawn) |
10//! | **Determinism** | 100% Deterministic | Subject to scheduler |
11//! | **State** | No real state (expectations) | Real state management |
12//! | **Use Case** | Unit testing logic *around* the client | Testing the actor itself or full system |
13//! | **Error Injection** | Easy (`return_err`) | Hard (requires specific state) |
14//!
15//! ## Testing Strategies
16//!
17//! The actor framework supports four distinct testing patterns.
18//!
19//! <details>
20//! <summary><b>Pattern 0: Client Logic Test (Pure Mock)</b></summary>
21//!
22//! **When to use**: Testing complex orchestration logic in your client wrappers without spinning up any actors.
23//!
24//! **Example**:
25//! ```rust
26//! use actor_framework::mock::MockClient;
27//! use actor_framework::{ActorEntity, ResourceClient, ResourceRequest};
28//! use async_trait::async_trait;
29//!
30//! // --- Define a minimal Entity for the test ---
31//! #[derive(Clone, Debug, PartialEq)]
32//! struct User { id: u32, email: String }
33//! #[derive(Debug)] struct UserCreate { email: String }
34//! #[derive(Debug)] struct UserUpdate;
35//! #[derive(Debug)] enum UserAction {}
36//! #[derive(Debug, thiserror::Error)] #[error("User error")] struct UserError;
37//!
38//! #[async_trait]
39//! impl ActorEntity for User {
40//!     type Id = u32; type Create = UserCreate; type Update = UserUpdate;
41//!     type Action = UserAction; type ActionResult = (); type Context = (); type Error = UserError;
42//!     fn from_create_params(id: u32, params: UserCreate) -> Result<Self, Self::Error> {
43//!         Ok(Self { id, email: params.email })
44//!     }
45//!     async fn on_update(&mut self, _: UserUpdate, _: &()) -> Result<(), Self::Error> { Ok(()) }
46//!     async fn handle_action(&mut self, _: UserAction, _: &()) -> Result<(), Self::Error> { Ok(()) }
47//! }
48//!
49//! // --- Define a minimal Client Wrapper ---
50//! struct UserClient { client: ResourceClient<User> }
51//! impl UserClient {
52//!     fn new(client: ResourceClient<User>) -> Self { Self { client } }
53//!     async fn get(&self, id: u32) -> Result<Option<User>, UserError> {
54//!         self.client.get(id).await.map_err(|_| UserError)
55//!     }
56//! }
57//!
58//! impl User {
59//!     fn new(id: u32, email: &str) -> Self { Self { id, email: email.to_string() } }
60//! }
61//!
62//! #[tokio::main]
63//! async fn main() {
64//!     // 1. Setup Mocks
65//!     let mut user_mock = MockClient::<User>::new();
66//!     user_mock.expect_get(1)
67//!         .return_ok(Some(User::new(1, "test@example.com")));
68//!
69//!     // 2. Create Client with Mocks
70//!     let user_client = UserClient::new(user_mock.client());
71//!     
72//!     // 3. Test Logic
73//!     let user = user_client.get(1).await.unwrap();
74//!     assert_eq!(user.unwrap().email, "test@example.com");
75//! }
76//! ```
77//! </details>
78//!
79//! <details>
80//! <summary><b>Pattern 1: Single Actor Test (Fast, Isolated)</b></summary>
81//!
82//! **When to use**: Testing a single actor's logic in isolation.
83//!
84//! **Example**:
85//! ```rust
86//! use actor_framework::{ActorEntity, ResourceActor, ResourceClient};
87//! use async_trait::async_trait;
88//!
89//! // --- Define Entity ---
90//! #[derive(Clone, Debug)] struct Product { id: u32, stock: u32 }
91//! #[derive(Debug)] struct ProductCreate { stock: u32 }
92//! #[derive(Debug)] struct ProductUpdate;
93//! #[derive(Debug)] enum ProductAction { CheckStock }
94//! #[derive(Debug, thiserror::Error)] #[error("Err")] struct ProductError;
95//!
96//! #[async_trait]
97//! impl ActorEntity for Product {
98//!     type Id = u32; type Create = ProductCreate; type Update = ProductUpdate;
99//!     type Action = ProductAction; type ActionResult = u32; type Context = (); type Error = ProductError;
100//!     fn from_create_params(id: u32, params: ProductCreate) -> Result<Self, Self::Error> {
101//!         Ok(Self { id, stock: params.stock })
102//!     }
103//!     async fn on_update(&mut self, _: ProductUpdate, _: &()) -> Result<(), Self::Error> { Ok(()) }
104//!     async fn handle_action(&mut self, action: ProductAction, _: &()) -> Result<u32, Self::Error> {
105//!         match action { ProductAction::CheckStock => Ok(self.stock) }
106//!     }
107//! }
108//!
109//! #[tokio::main]
110//! async fn main() {
111//!     let (actor, client) = ResourceActor::<Product>::new(10);
112//!     tokio::spawn(actor.run(()));
113//!     
114//!     let params = ProductCreate { stock: 100 };
115//!     let id = client.create(params).await.unwrap();
116//!     let stock = client.perform_action(id, ProductAction::CheckStock).await.unwrap();
117//!     assert_eq!(stock, 100);
118//! }
119//! ```
120//! </details>
121//!
122//! <details>
123//! <summary><b>Pattern 2: Actor with Mocked Dependencies (Sweet Spot)</b></summary>
124//!
125//! **When to use**: Testing an actor that depends on other actors, but you want to isolate the actor under test.
126//!
127//! **Example**:
128//! ```text
129//! This example requires multiple actors and is verbose to implement inline.
130//! See tests/order_actor_test.rs in the actor-recipe-app crate for a full example.
131//! ```
132//! </details>
133//!
134//! <details>
135//! <summary><b>Pattern 3: Full System Integration Test (Comprehensive)</b></summary>
136//!
137//! **When to use**: Testing the entire system working together, end-to-end flows, concurrency.
138//!
139//! See the `test_full_order_system_integration` function in `tests/integration_test.rs` for comprehensive examples.
140//! </details>
141//!
142//! ## Testing Failure Scenarios
143//!
144//! One of the biggest advantages of `MockClient` is the ability to simulate errors that are hard to reproduce with real actors (e.g., database timeouts, network partitions).
145//!
146//! ```rust
147//! use actor_framework::mock::MockClient;
148//! use actor_framework::{ActorEntity, FrameworkError};
149//! use async_trait::async_trait;
150//!
151//! #[derive(Clone, Debug)] struct User { id: u32 }
152//! #[derive(Debug)] struct UserCreate;
153//! #[derive(Debug)] struct UserUpdate;
154//! #[derive(Debug)] enum UserAction {}
155//! #[derive(Debug, thiserror::Error)] #[error("Err")] struct UserError;
156//!
157//! #[async_trait]
158//! impl ActorEntity for User {
159//!     type Id = u32; type Create = UserCreate; type Update = UserUpdate;
160//!     type Action = UserAction; type ActionResult = (); type Context = (); type Error = UserError;
161//!     fn from_create_params(id: u32, _: UserCreate) -> Result<Self, Self::Error> { Ok(Self { id }) }
162//!     async fn on_update(&mut self, _: UserUpdate, _: &()) -> Result<(), Self::Error> { Ok(()) }
163//!     async fn handle_action(&mut self, _: UserAction, _: &()) -> Result<(), Self::Error> { Ok(()) }
164//! }
165//!
166//! #[tokio::main]
167//! async fn main() {
168//!     let mut mock = MockClient::<User>::new();
169//!     let client = mock.client();
170//!
171//!     // Simulate a downstream failure
172//!     mock.expect_get(1)
173//!         .return_err(FrameworkError::ActorClosed);
174//!
175//!     // Verify your code handles it gracefully
176//!     let result = client.get(1).await;
177//!     assert!(matches!(result, Err(FrameworkError::ActorClosed)));
178//! }
179//! ```
180//!
181//! ## Advanced: Test-Only Actions
182//!
183//! <details>
184//! <summary><b>How to use Feature Flags for Testing</b></summary>
185//!
186//! Sometimes you need to inspect internal actor state for testing. Use a Cargo **feature flag** (`testing`)
187//! instead of `#[cfg(test)]` so it works with integration tests.
188//!
189//! ```toml
190//! [features]
191//! testing = []
192//! ```
193//!
194//! Then guard your test-only actions:
195//!
196//! ```rust,ignore
197//! pub enum ProductAction {
198//!     #[cfg(feature = "testing")]
199//!     GetInternalState,
200//! }
201//! ```
202//! </details>
203//!
204//! ## Mocking Utilities
205//!
206//! Use [`create_mock_client`] to get a client and a receiver, or use the fluent [`MockClient`] API.
207
208use crate::client::ResourceClient;
209use crate::entity::ActorEntity;
210use crate::error::FrameworkError;
211use crate::message::ResourceRequest;
212use std::collections::VecDeque;
213use std::sync::{Arc, Mutex};
214use tokio::sync::mpsc;
215
216// =============================================================================
217// EXPECTATION BUILDER API
218// =============================================================================
219
220/// Represents an expected request to the mock client.
221///
222/// This enum is used internally by `MockClient` to track what requests
223/// are expected and what responses should be returned.
224#[allow(dead_code)] // Future features: Update, Delete, Action expectations
225enum Expectation<T: ActorEntity> {
226    Get {
227        id: T::Id,
228        response: Result<Option<T>, FrameworkError>,
229    },
230    Create {
231        response: Result<T::Id, FrameworkError>,
232    },
233    Update {
234        id: T::Id,
235        response: Result<T, FrameworkError>,
236    },
237    Delete {
238        id: T::Id,
239        response: Result<(), FrameworkError>,
240    },
241    Action {
242        id: T::Id,
243        response: Result<T::ActionResult, FrameworkError>,
244    },
245}
246
247/// A mock client with expectation tracking for fluent testing.
248///
249/// # Example
250/// ```ignore
251/// let mut mock = MockClient::<User>::new();
252/// mock.expect_get("user_1".to_string()).return_ok(Some(user));
253/// mock.expect_create().return_ok("user_2".to_string());
254///
255/// let client = mock.client();
256/// // Use client in tests...
257/// mock.verify(); // Ensures all expectations were met
258/// ```
259pub struct MockClient<T: ActorEntity> {
260    client: ResourceClient<T>,
261    expectations: Arc<Mutex<VecDeque<Expectation<T>>>>,
262    _handle: tokio::task::JoinHandle<()>,
263}
264
265impl<T: ActorEntity + Send + 'static> Default for MockClient<T>
266where
267    T::Id: Send,
268    T::Create: Send,
269    T::Update: Send,
270    T::Action: Send,
271    T::ActionResult: Send,
272{
273    fn default() -> Self {
274        Self::new()
275    }
276}
277
278impl<T: ActorEntity + Send + 'static> MockClient<T>
279where
280    T::Id: Send,
281    T::Create: Send,
282    T::Update: Send,
283    T::Action: Send,
284    T::ActionResult: Send,
285{
286    /// Creates a new mock client with no expectations.
287    pub fn new() -> Self {
288        let (sender, mut receiver) = mpsc::channel::<ResourceRequest<T>>(100);
289        let expectations = Arc::new(Mutex::new(VecDeque::new()));
290        let expectations_clone = expectations.clone();
291
292        // Spawn background task to handle requests
293        let handle = tokio::spawn(async move {
294            while let Some(request) = receiver.recv().await {
295                let mut exps = expectations_clone.lock().unwrap();
296                let expectation = exps.pop_front();
297                drop(exps); // Release lock before async operations
298
299                match (request, expectation) {
300                    (
301                        ResourceRequest::Get { id: _, respond_to },
302                        Some(Expectation::Get { id: _, response }),
303                    ) => {
304                        let _ = respond_to.send(response);
305                    }
306                    (
307                        ResourceRequest::Create {
308                            params: _,
309                            respond_to,
310                        },
311                        Some(Expectation::Create { response }),
312                    ) => {
313                        let _ = respond_to.send(response);
314                    }
315                    (
316                        ResourceRequest::Update {
317                            id: _,
318                            update: _,
319                            respond_to,
320                        },
321                        Some(Expectation::Update { id: _, response }),
322                    ) => {
323                        let _ = respond_to.send(response);
324                    }
325                    (
326                        ResourceRequest::Delete { id: _, respond_to },
327                        Some(Expectation::Delete { id: _, response }),
328                    ) => {
329                        let _ = respond_to.send(response);
330                    }
331                    (
332                        ResourceRequest::Action {
333                            id: _,
334                            action: _,
335                            respond_to,
336                        },
337                        Some(Expectation::Action { id: _, response }),
338                    ) => {
339                        let _ = respond_to.send(response);
340                    }
341                    _ => {
342                        panic!("Unexpected request or expectation mismatch");
343                    }
344                }
345            }
346        });
347
348        Self {
349            client: ResourceClient::new(sender),
350            expectations,
351            _handle: handle,
352        }
353    }
354
355    /// Returns the client for use in tests.
356    pub fn client(&self) -> ResourceClient<T> {
357        self.client.clone()
358    }
359
360    /// Expects a `get` operation.
361    pub fn expect_get(&mut self, id: T::Id) -> GetExpectationBuilder<T> {
362        GetExpectationBuilder {
363            id,
364            expectations: self.expectations.clone(),
365        }
366    }
367
368    /// Expects a `create` operation.
369    pub fn expect_create(&mut self) -> CreateExpectationBuilder<T> {
370        CreateExpectationBuilder {
371            expectations: self.expectations.clone(),
372        }
373    }
374
375    /// Expects an `action` operation.
376    pub fn expect_action(&mut self, id: T::Id) -> ActionExpectationBuilder<T> {
377        ActionExpectationBuilder {
378            id,
379            expectations: self.expectations.clone(),
380        }
381    }
382
383    /// Verifies that all expectations were met.
384    pub fn verify(&self) {
385        let exps = self.expectations.lock().unwrap();
386        if !exps.is_empty() {
387            panic!("Not all expectations were met. {} remaining", exps.len());
388        }
389    }
390}
391
392/// Builder for `get` expectations.
393pub struct GetExpectationBuilder<T: ActorEntity> {
394    id: T::Id,
395    expectations: Arc<Mutex<VecDeque<Expectation<T>>>>,
396}
397
398impl<T: ActorEntity> GetExpectationBuilder<T> {
399    /// Sets the expectation to return a successful result.
400    pub fn return_ok(self, value: Option<T>) {
401        let mut exps = self.expectations.lock().unwrap();
402        exps.push_back(Expectation::Get {
403            id: self.id,
404            response: Ok(value),
405        });
406    }
407
408    /// Sets the expectation to return an error.
409    pub fn return_err(self, error: FrameworkError) {
410        let mut exps = self.expectations.lock().unwrap();
411        exps.push_back(Expectation::Get {
412            id: self.id,
413            response: Err(error),
414        });
415    }
416}
417
418/// Builder for `create` expectations.
419pub struct CreateExpectationBuilder<T: ActorEntity> {
420    expectations: Arc<Mutex<VecDeque<Expectation<T>>>>,
421}
422
423impl<T: ActorEntity> CreateExpectationBuilder<T> {
424    /// Sets the expectation to return a successful result.
425    pub fn return_ok(self, id: T::Id) {
426        let mut exps = self.expectations.lock().unwrap();
427        exps.push_back(Expectation::Create { response: Ok(id) });
428    }
429
430    /// Sets the expectation to return an error.
431    pub fn return_err(self, error: FrameworkError) {
432        let mut exps = self.expectations.lock().unwrap();
433        exps.push_back(Expectation::Create {
434            response: Err(error),
435        });
436    }
437}
438
439/// Builder for `action` expectations.
440pub struct ActionExpectationBuilder<T: ActorEntity> {
441    id: T::Id,
442    expectations: Arc<Mutex<VecDeque<Expectation<T>>>>,
443}
444
445impl<T: ActorEntity> ActionExpectationBuilder<T> {
446    /// Sets the expectation to return a successful result.
447    pub fn return_ok(self, result: T::ActionResult) {
448        let mut exps = self.expectations.lock().unwrap();
449        exps.push_back(Expectation::Action {
450            id: self.id,
451            response: Ok(result),
452        });
453    }
454
455    /// Sets the expectation to return an error.
456    pub fn return_err(self, error: FrameworkError) {
457        let mut exps = self.expectations.lock().unwrap();
458        exps.push_back(Expectation::Action {
459            id: self.id,
460            response: Err(error),
461        });
462    }
463}
464
465// =============================================================================
466// LEGACY HELPERS (for backward compatibility)
467// =============================================================================
468
469/// Creates a mock client and a receiver for asserting requests.
470///
471/// # Testing Strategy
472/// In unit/integration tests, we don't want to spin up a full `ResourceActor` if we are just
473/// testing the *Client* logic (e.g., `OrderClient`).
474///
475/// Instead, we create a "Mock Client". This client sends messages to a channel we control (`receiver`).
476/// We can then inspect the messages arriving on that channel and assert they are correct.
477/// This allows us to simulate the Actor's behavior (success, failure, delays) deterministically.
478///
479/// **Note**: Consider using [`MockClient`] for a more fluent API.
480pub fn create_mock_client<T: ActorEntity>(
481    buffer_size: usize,
482) -> (ResourceClient<T>, mpsc::Receiver<ResourceRequest<T>>) {
483    let (sender, receiver) = mpsc::channel(buffer_size);
484    (ResourceClient::new(sender), receiver)
485}
486
487/// Helper to verify that the next message is a Create request
488pub async fn expect_create<T: ActorEntity>(
489    receiver: &mut mpsc::Receiver<ResourceRequest<T>>,
490) -> Option<(
491    T::Create,
492    tokio::sync::oneshot::Sender<Result<T::Id, FrameworkError>>,
493)> {
494    match receiver.recv().await {
495        Some(ResourceRequest::Create { params, respond_to }) => Some((params, respond_to)),
496        _ => None,
497    }
498}
499
500/// Helper to verify that the next message is a Get request
501pub async fn expect_get<T: ActorEntity>(
502    receiver: &mut mpsc::Receiver<ResourceRequest<T>>,
503) -> Option<(
504    T::Id,
505    tokio::sync::oneshot::Sender<Result<Option<T>, FrameworkError>>,
506)> {
507    match receiver.recv().await {
508        Some(ResourceRequest::Get { id, respond_to }) => Some((id, respond_to)),
509        _ => None,
510    }
511}
512
513/// Helper to verify that the next message is an Action request
514pub async fn expect_action<T: ActorEntity>(
515    receiver: &mut mpsc::Receiver<ResourceRequest<T>>,
516) -> Option<(
517    T::Id,
518    T::Action,
519    tokio::sync::oneshot::Sender<Result<T::ActionResult, FrameworkError>>,
520)> {
521    match receiver.recv().await {
522        Some(ResourceRequest::Action {
523            id,
524            action,
525            respond_to,
526        }) => Some((id, action, respond_to)),
527        _ => None,
528    }
529}
530
531#[cfg(test)]
532mod tests {
533    use super::*;
534    use crate::entity::ActorEntity;
535    use async_trait::async_trait;
536
537    #[derive(Clone, Debug, PartialEq)]
538    struct User {
539        id: u32,
540        name: String,
541        email: String,
542    }
543
544    #[derive(Debug)]
545    struct UserCreate {
546        name: String,
547        email: String,
548    }
549
550    #[derive(Debug)]
551    struct UserUpdate;
552
553    #[derive(Debug)]
554    enum UserAction {}
555
556    #[derive(Debug, thiserror::Error)]
557    #[error("User error")]
558    struct UserError;
559
560    #[async_trait]
561    impl ActorEntity for User {
562        type Id = u32;
563        type Create = UserCreate;
564        type Update = UserUpdate;
565        type Action = UserAction;
566        type ActionResult = ();
567        type Context = ();
568        type Error = UserError;
569
570        fn from_create_params(id: u32, params: UserCreate) -> Result<Self, Self::Error> {
571            Ok(Self {
572                id,
573                name: params.name,
574                email: params.email,
575            })
576        }
577        async fn on_update(
578            &mut self,
579            _update: UserUpdate,
580            _ctx: &Self::Context,
581        ) -> Result<(), Self::Error> {
582            Ok(())
583        }
584
585        async fn handle_action(
586            &mut self,
587            _action: UserAction,
588            _ctx: &Self::Context,
589        ) -> Result<(), Self::Error> {
590            Ok(())
591        }
592    }
593
594    impl User {
595        fn new(id: u32, email: &str) -> Self {
596            Self {
597                id,
598                name: "Test User".to_string(),
599                email: email.to_string(),
600            }
601        }
602    }
603
604    #[tokio::test]
605    async fn test_mock_client() {
606        let (client, mut receiver) = create_mock_client::<User>(10);
607
608        // Test Create
609        let create_task = tokio::spawn(async move {
610            let user = UserCreate {
611                name: "Test".to_string(),
612                email: "test@example.com".to_string(),
613            };
614            client.create(user).await
615        });
616
617        let (payload, responder) = expect_create(&mut receiver)
618            .await
619            .expect("Expected Create request");
620        assert_eq!(payload.name, "Test");
621        responder.send(Ok(1)).unwrap();
622
623        let result = create_task.await.unwrap();
624        assert!(matches!(result, Ok(id) if id == 1));
625    }
626
627    #[tokio::test]
628    async fn test_mock_client_with_expectations() {
629        // Create mock with fluent expectation API
630        let mut mock = MockClient::<User>::new();
631
632        // Set up expectations
633        mock.expect_create().return_ok(1);
634        mock.expect_get(1)
635            .return_ok(Some(User::new(1, "test@example.com")));
636
637        let client = mock.client();
638
639        // Execute operations
640        let user = UserCreate {
641            name: "Test".to_string(),
642            email: "test@example.com".to_string(),
643        };
644        let id = client.create(user).await.unwrap();
645        assert_eq!(id, 1);
646
647        let fetched = client.get(1).await.unwrap();
648        assert!(fetched.is_some());
649        assert_eq!(fetched.unwrap().email, "test@example.com");
650
651        // Verify all expectations were met
652        mock.verify();
653    }
654}