diff --git a/book/SUMMARY.md b/book/SUMMARY.md index 732d33c7..d3461cd5 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -9,7 +9,7 @@ - [Providers]() - [Http](./providers/http.md) - [IPC](./providers/ipc.md) - - [Mock]() + - [Mock](./providers/mock.md) - [Quorum](./providers/quorum.md) - [Retry](./providers/retry.md) - [RW](./providers/rw.md) diff --git a/book/providers/mock.md b/book/providers/mock.md new file mode 100644 index 00000000..2363bf71 --- /dev/null +++ b/book/providers/mock.md @@ -0,0 +1,5 @@ +# Mock provider + +```rust +{{#include ../../examples/providers/examples/mock.rs}} +``` \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 8bc4d73d..bb25a09d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -34,10 +34,10 @@ - [x] Signer - [ ] Time lag - [ ] Transformer -- [ ] Providers +- [x] Providers - [x] Http - [x] IPC - - [ ] Mock + - [x] Mock - [x] Quorum - [x] Retry - [x] RW diff --git a/examples/providers/examples/mock.rs b/examples/providers/examples/mock.rs new file mode 100644 index 00000000..4d80fe57 --- /dev/null +++ b/examples/providers/examples/mock.rs @@ -0,0 +1,80 @@ +use ethers::prelude::*; + +/// `MockProvider` is a mock Ethereum provider that can be used for testing purposes. +/// It allows to simulate Ethereum state and behavior, by explicitly instructing +/// provider's responses on client requests. +/// +/// This can be useful for testing code that relies on providers without the need to +/// connect to a real network or spend real Ether. It also allows to test code in a +/// deterministic manner, as you can control the state and behavior of the provider. +/// +/// In these examples we use the common Arrange, Act, Assert (AAA) test approach. +/// It is a useful pattern for well-structured, understandable and maintainable tests. +#[tokio::main] +async fn main() -> eyre::Result<()> { + mocked_block_number().await?; + mocked_provider_dependency().await?; + Ok(()) +} + +async fn mocked_block_number() -> eyre::Result<()> { + // Arrange + let mock = MockProvider::new(); + let block_num_1 = U64::from(1); + let block_num_2 = U64::from(2); + let block_num_3 = U64::from(3); + // Mock responses are organized in a stack (LIFO) + mock.push(block_num_1)?; + mock.push(block_num_2)?; + mock.push(block_num_3)?; + + // Act + let ret_block_3: U64 = JsonRpcClient::request(&mock, "eth_blockNumber", ()).await?; + let ret_block_2: U64 = JsonRpcClient::request(&mock, "eth_blockNumber", ()).await?; + let ret_block_1: U64 = JsonRpcClient::request(&mock, "eth_blockNumber", ()).await?; + + // Assert + assert_eq!(block_num_1, ret_block_1); + assert_eq!(block_num_2, ret_block_2); + assert_eq!(block_num_3, ret_block_3); + + Ok(()) +} + +/// Here we test the `OddBlockOracle` struct (defined below) that relies +/// on a Provider to perform some logics. +/// The Provider reference is expressed with trait bounds, enforcing lose coupling, +/// maintainability and testability. +async fn mocked_provider_dependency() -> eyre::Result<()> { + // Arrange + let (provider, mock) = crate::Provider::mocked(); + mock.push(U64::from(2))?; + + // Act + // Let's mock the provider dependency (we ❤️ DI!) then ask for the answer + let oracle = OddBlockOracle::new(provider); + let answer: bool = oracle.is_odd_block().await?; + + // Assert + assert!(answer); + Ok(()) +} + +struct OddBlockOracle

{ + provider: Provider

, +} + +impl

OddBlockOracle

+where + P: JsonRpcClient, +{ + fn new(provider: Provider

) -> Self { + Self { provider } + } + + /// We want to test this! + async fn is_odd_block(&self) -> eyre::Result { + let block: U64 = self.provider.get_block_number().await?; + Ok(block % 2 == U64::zero()) + } +} diff --git a/examples/providers/examples/ws.rs b/examples/providers/examples/ws.rs index 9268ce38..e4aded8a 100644 --- a/examples/providers/examples/ws.rs +++ b/examples/providers/examples/ws.rs @@ -4,20 +4,18 @@ use ethers::prelude::*; const WSS_URL: &str = "wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27"; -type BoxErr = Box; - /// The Ws transport allows you to send JSON-RPC requests and receive responses over WebSocket /// connections. It is useful for connecting to Ethereum nodes that support WebSockets. /// This allows to interact with the Ethereum network in real-time without the need for HTTP /// polling. #[tokio::main] -async fn main() -> Result<(), BoxErr> { +async fn main() -> eyre::Result<()> { create_instance().await?; watch_blocks().await?; Ok(()) } -async fn create_instance() -> Result<(), BoxErr> { +async fn create_instance() -> eyre::Result<()> { // An Ws provider can be created from an ws(s) URI. // In case of wss you must add the "rustls" or "openssl" feature // to the ethers library dependency in `Cargo.toml`. @@ -39,7 +37,7 @@ async fn create_instance() -> Result<(), BoxErr> { } /// Let's show how the Ws connection enables listening for blocks using a persistent TCP connection -async fn watch_blocks() -> Result<(), BoxErr> { +async fn watch_blocks() -> eyre::Result<()> { let provider = Provider::::connect(WSS_URL).await?; let mut stream = provider.watch_blocks().await?.take(1);