docs: mock-provider (#2011)

* MockProvider example + mdbook reference

* review: use eyre::Result

Co-authored-by: Andrea Simeoni <>
This commit is contained in:
Andrea Simeoni 2023-01-05 16:55:23 +01:00 committed by GitHub
parent 3ed83d5dd3
commit 7e6c3ba983
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 91 additions and 8 deletions

View File

@ -9,7 +9,7 @@
- [Providers]() - [Providers]()
- [Http](./providers/http.md) - [Http](./providers/http.md)
- [IPC](./providers/ipc.md) - [IPC](./providers/ipc.md)
- [Mock]() - [Mock](./providers/mock.md)
- [Quorum](./providers/quorum.md) - [Quorum](./providers/quorum.md)
- [Retry](./providers/retry.md) - [Retry](./providers/retry.md)
- [RW](./providers/rw.md) - [RW](./providers/rw.md)

5
book/providers/mock.md Normal file
View File

@ -0,0 +1,5 @@
# Mock provider
```rust
{{#include ../../examples/providers/examples/mock.rs}}
```

View File

@ -34,10 +34,10 @@
- [x] Signer - [x] Signer
- [ ] Time lag - [ ] Time lag
- [ ] Transformer - [ ] Transformer
- [ ] Providers - [x] Providers
- [x] Http - [x] Http
- [x] IPC - [x] IPC
- [ ] Mock - [x] Mock
- [x] Quorum - [x] Quorum
- [x] Retry - [x] Retry
- [x] RW - [x] RW

View File

@ -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<P> {
provider: Provider<P>,
}
impl<P> OddBlockOracle<P>
where
P: JsonRpcClient,
{
fn new(provider: Provider<P>) -> Self {
Self { provider }
}
/// We want to test this!
async fn is_odd_block(&self) -> eyre::Result<bool> {
let block: U64 = self.provider.get_block_number().await?;
Ok(block % 2 == U64::zero())
}
}

View File

@ -4,20 +4,18 @@ use ethers::prelude::*;
const WSS_URL: &str = "wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27"; const WSS_URL: &str = "wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27";
type BoxErr = Box<dyn std::error::Error>;
/// The Ws transport allows you to send JSON-RPC requests and receive responses over WebSocket /// 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. /// 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 /// This allows to interact with the Ethereum network in real-time without the need for HTTP
/// polling. /// polling.
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), BoxErr> { async fn main() -> eyre::Result<()> {
create_instance().await?; create_instance().await?;
watch_blocks().await?; watch_blocks().await?;
Ok(()) Ok(())
} }
async fn create_instance() -> Result<(), BoxErr> { async fn create_instance() -> eyre::Result<()> {
// An Ws provider can be created from an ws(s) URI. // An Ws provider can be created from an ws(s) URI.
// In case of wss you must add the "rustls" or "openssl" feature // In case of wss you must add the "rustls" or "openssl" feature
// to the ethers library dependency in `Cargo.toml`. // 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 /// 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::<Ws>::connect(WSS_URL).await?; let provider = Provider::<Ws>::connect(WSS_URL).await?;
let mut stream = provider.watch_blocks().await?.take(1); let mut stream = provider.watch_blocks().await?.take(1);