Skip to main content

ic_canister_runtime/stub/
mod.rs

1#[cfg(test)]
2mod tests;
3
4use crate::{IcError, Runtime};
5use async_trait::async_trait;
6use candid::{utils::ArgumentEncoder, CandidType, Decode, Encode, Principal};
7use serde::de::DeserializeOwned;
8use std::{collections::VecDeque, sync::Mutex};
9
10/// An implementation of [`Runtime`] that returns pre-defined results from a queue.
11/// This runtime is primarily intended for testing purposes.
12///
13/// # Examples
14///
15/// ```rust
16/// # #[tokio::main]
17/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
18/// use candid::Principal;
19/// use ic_canister_runtime::{IcError, Runtime, StubRuntime};
20///
21/// const PRINCIPAL: Principal = Principal::from_slice(&[0x9d, 0xf7, 0x01]);
22/// const METHOD: &str = "method";
23/// const ARGS: (&str,) = ("args",);
24///
25/// let runtime = StubRuntime::new()
26///     .add_stub_response(1_u64)
27///     .add_stub_response("two")
28///     .add_stub_response(Some(3_u128));
29///
30/// let result_1: Result<u64, IcError> = runtime
31///     .update_call(PRINCIPAL, METHOD, ARGS, 0)
32///     .await;
33/// assert_eq!(result_1, Ok(1_u64));
34///
35/// let result_2: Result<String, IcError> = runtime
36///     .query_call(PRINCIPAL, METHOD, ARGS)
37///     .await;
38/// assert_eq!(result_2, Ok("two".to_string()));
39///
40/// let result_3: Result<Option<u128>, IcError> = runtime
41///     .update_call(PRINCIPAL, METHOD, ARGS, 0)
42///     .await;
43/// assert_eq!(result_3, Ok(Some
44/// (3_u128)));
45/// # Ok(())
46/// # }
47/// ```
48#[derive(Debug, Default)]
49pub struct StubRuntime {
50    // Use a mutex so that this struct is Send and Sync
51    call_results: Mutex<VecDeque<Vec<u8>>>,
52}
53
54impl StubRuntime {
55    /// Create a new empty [`StubRuntime`].
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    /// Mutate the [`StubRuntime`] instance to add the given stub response.
61    ///
62    /// Panics if the stub response cannot be encoded using Candid.
63    pub fn add_stub_response<Out: CandidType>(self, stub_response: Out) -> Self {
64        let result = Encode!(&stub_response).expect("Failed to encode Candid stub response");
65        self.call_results.try_lock().unwrap().push_back(result);
66        self
67    }
68
69    fn call<Out>(&self) -> Result<Out, IcError>
70    where
71        Out: CandidType + DeserializeOwned,
72    {
73        let bytes = self
74            .call_results
75            .try_lock()
76            .unwrap()
77            .pop_front()
78            .unwrap_or_else(|| panic!("No available call response"));
79        Ok(Decode!(&bytes, Out).expect("Failed to decode Candid stub response"))
80    }
81}
82
83#[async_trait]
84impl Runtime for StubRuntime {
85    async fn update_call<In, Out>(
86        &self,
87        _id: Principal,
88        _method: &str,
89        _args: In,
90        _cycles: u128,
91    ) -> Result<Out, IcError>
92    where
93        In: ArgumentEncoder + Send,
94        Out: CandidType + DeserializeOwned,
95    {
96        self.call()
97    }
98
99    async fn query_call<In, Out>(
100        &self,
101        _id: Principal,
102        _method: &str,
103        _args: In,
104    ) -> Result<Out, IcError>
105    where
106        In: ArgumentEncoder + Send,
107        Out: CandidType + DeserializeOwned,
108    {
109        self.call()
110    }
111}