alloy_transport/
mock.rs

1//! Mock transport and utility types.
2//!
3//! [`MockTransport`] returns responses that have been pushed into its associated [`Asserter`]'s
4//! queue using FIFO.
5//!
6//! # Examples
7//!
8//! ```ignore (dependency cycle)
9//! use alloy_transport::mock::*;
10//!
11//! let asserter = Asserter::new();
12//! let provider = ProviderBuilder::new()
13//!     /* ... */
14//!     .on_mocked_client(asserter.clone());
15//!
16//! let n = 12345;
17//! asserter.push_success(&n);
18//! let actual = provider.get_block_number().await.unwrap();
19//! assert_eq!(actual, n);
20//! ```
21
22use crate::{TransportErrorKind, TransportResult};
23use alloy_json_rpc as j;
24use serde::Serialize;
25use std::{
26    borrow::Cow,
27    collections::VecDeque,
28    sync::{Arc, PoisonError, RwLock},
29};
30
31/// A mock response that can be pushed into an [`Asserter`].
32pub type MockResponse = j::ResponsePayload;
33
34/// Container for pushing responses into a [`MockTransport`].
35///
36/// Mock responses are stored and returned with a FIFO queue.
37///
38/// See the [module documentation][self].
39#[derive(Debug, Clone, Default)]
40pub struct Asserter {
41    responses: Arc<RwLock<VecDeque<MockResponse>>>,
42}
43
44impl Asserter {
45    /// Instantiate a new asserter.
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Push a response into the queue.
51    pub fn push(&self, response: MockResponse) {
52        self.write_q().push_back(response);
53    }
54
55    /// Insert a successful response into the queue.
56    ///
57    /// # Panics
58    ///
59    /// Panics if serialization fails.
60    #[track_caller]
61    pub fn push_success<R: Serialize>(&self, response: &R) {
62        let s = serde_json::to_string(response).unwrap();
63        self.push(MockResponse::Success(serde_json::value::RawValue::from_string(s).unwrap()));
64    }
65
66    /// Push an error payload into the queue.
67    pub fn push_failure(&self, error: j::ErrorPayload) {
68        self.push(MockResponse::Failure(error));
69    }
70
71    /// Push an internal error message into the queue.
72    pub fn push_failure_msg(&self, msg: impl Into<Cow<'static, str>>) {
73        self.push_failure(j::ErrorPayload::internal_error_message(msg.into()));
74    }
75
76    /// Pops the next mock response.
77    pub fn pop_response(&self) -> Option<MockResponse> {
78        self.write_q().pop_front()
79    }
80
81    /// Returns a read lock guard to the responses queue.
82    pub fn read_q(&self) -> impl std::ops::Deref<Target = VecDeque<MockResponse>> + '_ {
83        self.responses.read().unwrap_or_else(PoisonError::into_inner)
84    }
85
86    /// Returns a write lock guard to the responses queue.
87    pub fn write_q(&self) -> impl std::ops::DerefMut<Target = VecDeque<MockResponse>> + '_ {
88        self.responses.write().unwrap_or_else(PoisonError::into_inner)
89    }
90}
91
92/// A transport that returns responses from an associated [`Asserter`].
93///
94/// See the [module documentation][self].
95#[derive(Clone, Debug)]
96pub struct MockTransport {
97    asserter: Asserter,
98}
99
100impl MockTransport {
101    /// Create a new [`MockTransport`] with the given [`Asserter`].
102    pub const fn new(asserter: Asserter) -> Self {
103        Self { asserter }
104    }
105
106    /// Return a reference to the associated [`Asserter`].
107    pub const fn asserter(&self) -> &Asserter {
108        &self.asserter
109    }
110
111    async fn handle(self, req: j::RequestPacket) -> TransportResult<j::ResponsePacket> {
112        Ok(match req {
113            j::RequestPacket::Single(req) => j::ResponsePacket::Single(self.map_request(req)?),
114            j::RequestPacket::Batch(reqs) => j::ResponsePacket::Batch(
115                reqs.into_iter()
116                    .map(|req| self.map_request(req))
117                    .collect::<TransportResult<_>>()?,
118            ),
119        })
120    }
121
122    fn map_request(&self, req: j::SerializedRequest) -> TransportResult<j::Response> {
123        Ok(j::Response {
124            id: req.id().clone(),
125            payload: self
126                .asserter
127                .pop_response()
128                .ok_or_else(|| TransportErrorKind::custom_str("empty asserter response queue"))?,
129        })
130    }
131}
132
133impl std::ops::Deref for MockTransport {
134    type Target = Asserter;
135
136    fn deref(&self) -> &Self::Target {
137        &self.asserter
138    }
139}
140
141impl tower::Service<j::RequestPacket> for MockTransport {
142    type Response = j::ResponsePacket;
143    type Error = crate::TransportError;
144    type Future = crate::TransportFut<'static>;
145
146    fn poll_ready(
147        &mut self,
148        _cx: &mut std::task::Context<'_>,
149    ) -> std::task::Poll<Result<(), Self::Error>> {
150        std::task::Poll::Ready(Ok(()))
151    }
152
153    fn call(&mut self, req: j::RequestPacket) -> Self::Future {
154        Box::pin(self.clone().handle(req))
155    }
156}
157
158// Tests are in `providers/tests/it/mock.rs`.