Info
This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: How can I mock tokio::io::AsyncReadExt and AsyncWriteExt traits?
My goal is to have unit tests inject a factory trait (called Networker) implementation into my system under test that returns an opaque implementation of AsyncReadExt + AsyncWriteExt + Unpin from its Networker::connect() function such that I can have the same functions I need to call in tokio::net::TcpStream in the system under test behave arbitrarily to suit the test conditions. I'm hoping to use mockall to mock up the AsyncReadExt and AsyncWriteExt traits, which also requires mocking AsyncRead and AsyncWrite. I've gotten somewhat close with the following:
/**
* Mockable trait that combines the same read/write traits we need our Networker::connect() DI
* function to return in the system under test. This allows us to install test specific behavior via expectations
* for the opaque read/write impl returned in the system under test and thereby explore TCP read/write edge cases.
*/
trait AsyncReadWriteStream: AsyncReadExt + AsyncWriteExt + Unpin {}
mock! {
AsyncReadWriteStream {}
impl AsyncRead for AsyncReadWriteStream {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<Result<(), std::io::Error>>;
}
impl AsyncReadExt for AsyncReadWriteStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>;
}
impl AsyncWrite for AsyncReadWriteStream {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, std::io::Error>>;
fn poll_flush(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Result<(), std::io::Error>>;
fn poll_shutdown(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Result<(), std::io::Error>>;
}
impl AsyncWriteExt for AsyncReadWriteStream {
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()>;
}
}
This complains about wanting named lifetime params at the '_ points, so I added in some:
trait AsyncReadWriteStream: AsyncReadExt + AsyncWriteExt + Unpin {}
mock! {
AsyncReadWriteStream {}
impl AsyncRead for AsyncReadWriteStream {
fn poll_read<'a>(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'a>,
buf: &mut ReadBuf<'a>,
) -> Poll<Result<(), std::io::Error>>;
}
impl AsyncReadExt for AsyncReadWriteStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>;
}
impl AsyncWrite for AsyncReadWriteStream {
fn poll_write<'a>(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'a>,
buf: &[u8],
) -> Poll<Result<usize, std::io::Error>>;
fn poll_flush<'a>(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'a>,
) -> Poll<Result<(), std::io::Error>>;
fn poll_shutdown<'a>(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'a>,
) -> Poll<Result<(), std::io::Error>>;
}
impl AsyncWriteExt for AsyncReadWriteStream {
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()>;
}
}
But that gets me to a confusing "conflicting implementations" error:
error[E0119]: conflicting implementations of trait `tokio::io::AsyncWriteExt` for type `MockAsyncReadWriteStream`
--> ...
|
1627 | / mock! {
1628 | | AsyncReadWriteStream {}
1629 | | impl AsyncRead for AsyncReadWriteStream {
1630 | | fn poll_read<'a>(
... |
1654 | | impl AsyncWriteExt for AsyncReadWriteStream {
| |___________________________________________________^
|
= note: conflicting implementation in crate `tokio`:
- impl<W> tokio::io::AsyncWriteExt for W
where W: tokio::io::AsyncWrite, W: ?Sized;
= note: this error originates in the macro `mock` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0119]: conflicting implementations of trait `tokio::io::AsyncReadExt` for type `MockAsyncReadWriteStream`
--> ...
|
1627 | / mock! {
1628 | | AsyncReadWriteStream {}
1629 | | impl AsyncRead for AsyncReadWriteStream {
1630 | | fn poll_read<'a>(
... |
1636 | | impl AsyncReadExt for AsyncReadWriteStream {
| |__________________________________________________^
|
= note: conflicting implementation in crate `tokio`:
- impl<R> tokio::io::AsyncReadExt for R
where R: tokio::io::AsyncRead, R: ?Sized;
= note: this error originates in the macro `mock` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `impl` item signature doesn't match `trait` item signature
--> ...
|
1627 | / mock! {
1628 | | AsyncReadWriteStream {}
1629 | | impl AsyncRead for AsyncReadWriteStream {
1630 | | fn poll_read<'a>(
... |
1657 | | }
| |_____^ found `fn(Pin<&'1 mut MockAsyncReadWriteStream>, &'2 mut std::task::Context<'3>, &mut tokio::io::ReadBuf<'3>) -> std::task::Poll<std::result::Result<(), std::io::Error>>`
|
::: C:\Users\jcreswell\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\tokio-1.44.2\src\io\async_read.rs:54:5
|
54 | / fn poll_read(
55 | | self: Pin<&mut Self>,
56 | | cx: &mut Context<'_>,
57 | | buf: &mut ReadBuf<'_>,
58 | | ) -> Poll<io::Result<()>>;
| |______________________________- expected `fn(Pin<&'1 mut MockAsyncReadWriteStream>, &'2 mut std::task::Context<'3>, &mut tokio::io::ReadBuf<'_>) -> std::task::Poll<std::result::Result<(), std::io::Error>>`
|
= note: expected signature `fn(Pin<&'1 mut MockAsyncReadWriteStream>, &'2 mut std::task::Context<'3>, &mut tokio::io::ReadBuf<'_>) -> std::task::Poll<std::result::Result<(), std::io::Error>>`
found signature `fn(Pin<&'1 mut MockAsyncReadWriteStream>, &'2 mut std::task::Context<'3>, &mut tokio::io::ReadBuf<'3>) -> std::task::Poll<std::result::Result<(), std::io::Error>>`
= help: the lifetime requirements from the `impl` do not correspond to the requirements in the `trait`
= help: verify the lifetime relationships in the `trait` and `impl` between the `self` argument, the other inputs and its output
= note: this error originates in the macro `mock` (in Nightly builds, run with -Z macro-backtrace for more info)
and changing just ReadBuf param back to ReadBuf<'_> just reintroduces the "expected named lifetime parameter" error. I also tried using the exact function signatures for AsyncReadExt and AsyncWriteExt e.g. fn write_all<'a>(&'a mut self, src: &'a [u8]) -> WriteAll<'a, Self>
but the return struct WriteAll is part of the private io::util module in tokio.
How should I set up mock!{} such that my mock implementation of AsyncReadExt, AsyncWriteExt, and their dependencies satisfy all the constraints of the tokio implementations?
9 posts - 3 participants
🏷️ rust_feed