ethers_providers/rpc/transports/
mock.rs

1use crate::{JsonRpcClient, ProviderError};
2use async_trait::async_trait;
3use serde::{de::DeserializeOwned, Serialize};
4use serde_json::Value;
5use std::{
6    borrow::Borrow,
7    collections::VecDeque,
8    sync::{Arc, Mutex},
9};
10use thiserror::Error;
11
12/// Helper type that can be used to pass through the `params` value.
13/// This is necessary because the wrapper provider is supposed to skip the `params` if it's of
14/// size 0, see `crate::transports::common::Request`
15#[derive(Debug)]
16enum MockParams {
17    Value(Value),
18    Zst,
19}
20
21/// Helper response type for `MockProvider`, allowing custom JSON-RPC errors to be provided.
22/// `Value` for successful responses, `Error` for JSON-RPC errors.
23#[derive(Clone, Debug)]
24pub enum MockResponse {
25    /// Successful response with a `serde_json::Value`.
26    Value(Value),
27
28    /// Error response with a `JsonRpcError`.
29    Error(super::JsonRpcError),
30}
31
32#[derive(Clone, Debug)]
33/// Mock transport used in test environments.
34pub struct MockProvider {
35    requests: Arc<Mutex<VecDeque<(String, MockParams)>>>,
36    responses: Arc<Mutex<VecDeque<MockResponse>>>,
37}
38
39impl Default for MockProvider {
40    fn default() -> Self {
41        Self::new()
42    }
43}
44
45#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
46#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
47impl JsonRpcClient for MockProvider {
48    type Error = MockError;
49
50    /// Pushes the `(method, params)` to the back of the `requests` queue,
51    /// pops the responses from the back of the `responses` queue
52    async fn request<T: Serialize + Send + Sync, R: DeserializeOwned>(
53        &self,
54        method: &str,
55        params: T,
56    ) -> Result<R, MockError> {
57        let params = if std::mem::size_of::<T>() == 0 {
58            MockParams::Zst
59        } else {
60            MockParams::Value(serde_json::to_value(params)?)
61        };
62        self.requests.lock().unwrap().push_back((method.to_owned(), params));
63        let mut data = self.responses.lock().unwrap();
64        let element = data.pop_back().ok_or(MockError::EmptyResponses)?;
65        match element {
66            MockResponse::Value(value) => {
67                let res: R = serde_json::from_value(value)?;
68                Ok(res)
69            }
70            MockResponse::Error(error) => Err(MockError::JsonRpcError(error)),
71        }
72    }
73}
74
75impl MockProvider {
76    /// Checks that the provided request was submitted by the client
77    pub fn assert_request<T: Serialize + Send + Sync>(
78        &self,
79        method: &str,
80        data: T,
81    ) -> Result<(), MockError> {
82        let (m, inp) = self.requests.lock().unwrap().pop_front().ok_or(MockError::EmptyRequests)?;
83        assert_eq!(m, method);
84        assert!(!matches!(inp, MockParams::Value(serde_json::Value::Null)));
85        if std::mem::size_of::<T>() == 0 {
86            assert!(matches!(inp, MockParams::Zst));
87        } else if let MockParams::Value(inp) = inp {
88            assert_eq!(serde_json::to_value(data).expect("could not serialize data"), inp);
89        } else {
90            unreachable!("Zero sized types must be denoted with MockParams::Zst")
91        }
92
93        Ok(())
94    }
95
96    /// Instantiates a mock transport
97    pub fn new() -> Self {
98        Self {
99            requests: Arc::new(Mutex::new(VecDeque::new())),
100            responses: Arc::new(Mutex::new(VecDeque::new())),
101        }
102    }
103
104    /// Pushes the data to the responses
105    pub fn push<T: Serialize + Send + Sync, K: Borrow<T>>(&self, data: K) -> Result<(), MockError> {
106        let value = serde_json::to_value(data.borrow())?;
107        self.responses.lock().unwrap().push_back(MockResponse::Value(value));
108        Ok(())
109    }
110
111    /// Pushes the data or error to the responses
112    pub fn push_response(&self, response: MockResponse) {
113        self.responses.lock().unwrap().push_back(response);
114    }
115}
116
117#[derive(Error, Debug)]
118/// Errors for the `MockProvider`
119pub enum MockError {
120    /// (De)Serialization error
121    #[error(transparent)]
122    SerdeJson(#[from] serde_json::Error),
123
124    /// Empty requests array
125    #[error("empty requests array, please push some requests")]
126    EmptyRequests,
127
128    /// Empty responses array
129    #[error("empty responses array, please push some responses")]
130    EmptyResponses,
131
132    /// Custom JsonRpcError
133    #[error("JSON-RPC error: {0}")]
134    JsonRpcError(super::JsonRpcError),
135}
136
137impl crate::RpcError for MockError {
138    fn as_error_response(&self) -> Option<&super::JsonRpcError> {
139        match self {
140            MockError::JsonRpcError(e) => Some(e),
141            _ => None,
142        }
143    }
144
145    fn as_serde_error(&self) -> Option<&serde_json::Error> {
146        match self {
147            MockError::SerdeJson(e) => Some(e),
148            _ => None,
149        }
150    }
151}
152
153impl From<MockError> for ProviderError {
154    fn from(src: MockError) -> Self {
155        ProviderError::JsonRpcClientError(Box::new(src))
156    }
157}
158
159#[cfg(test)]
160#[cfg(not(target_arch = "wasm32"))]
161mod tests {
162    use super::*;
163    use crate::{JsonRpcError, Middleware};
164    use ethers_core::types::U64;
165
166    #[tokio::test]
167    async fn pushes_request_and_response() {
168        let mock = MockProvider::new();
169        mock.push(U64::from(12)).unwrap();
170        let block: U64 = mock.request("eth_blockNumber", ()).await.unwrap();
171        mock.assert_request("eth_blockNumber", ()).unwrap();
172        assert_eq!(block.as_u64(), 12);
173    }
174
175    #[tokio::test]
176    async fn empty_responses() {
177        let mock = MockProvider::new();
178        // tries to get a response without pushing a response
179        let err = mock.request::<_, ()>("eth_blockNumber", ()).await.unwrap_err();
180        match err {
181            MockError::EmptyResponses => {}
182            _ => panic!("expected empty responses"),
183        };
184    }
185
186    #[tokio::test]
187    async fn pushes_error_response() {
188        let mock = MockProvider::new();
189        let error = JsonRpcError {
190            code: 3,
191            data: Some(serde_json::from_str(r#""0x556f1830...""#).unwrap()),
192            message: "execution reverted".to_string(),
193        };
194        mock.push_response(MockResponse::Error(error.clone()));
195
196        let result: Result<U64, MockError> = mock.request("eth_blockNumber", ()).await;
197        match result {
198            Err(MockError::JsonRpcError(e)) => {
199                assert_eq!(e.code, error.code);
200                assert_eq!(e.message, error.message);
201                assert_eq!(e.data, error.data);
202            }
203            _ => panic!("Expected JsonRpcError"),
204        }
205    }
206
207    #[tokio::test]
208    async fn empty_requests() {
209        let mock = MockProvider::new();
210        // tries to assert a request without making one
211        let err = mock.assert_request("eth_blockNumber", ()).unwrap_err();
212        match err {
213            MockError::EmptyRequests => {}
214            _ => panic!("expected empty request"),
215        };
216    }
217
218    #[tokio::test]
219    async fn composes_with_provider() {
220        let (provider, mock) = crate::Provider::mocked();
221
222        mock.push(U64::from(12)).unwrap();
223        let block = provider.get_block_number().await.unwrap();
224        assert_eq!(block.as_u64(), 12);
225    }
226}