-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Question: How to mock SubsystemHandle for testing? #52
Comments
Hmmm... I don't have anything built-in for this yet. I might have to introduce a trait that represents the SubsystemHandle so you can do a proper dependency injection. Do those words mean anything to you? :) if not, I can explain. Either way it might require changes to this library. |
Yes, I got the idea thanks for the help! I think it would be a good idea to provide such trait in general. Once I have more experience, I would like to help with a PR for this :) |
I gave it some thought, and I'm unsure why calling 'Toplevel::new' every time is a bad idea. It doesn't have any side effects and should be quite performant. I do it at my tests as well. I tried coming up with a mocking interface and failed; async traits are not implemented yet, and even if we use something like the async-trait crate it would be unclear what exactly would have to be mocked, how calling the mock functions would have to behave, etc. It might be easier to mock on your side, so you can control it yourself. Like this: use std::{
sync::{
atomic::{AtomicU32, Ordering},
Arc,
},
time::Duration,
};
use async_trait::async_trait;
use tokio_graceful_shutdown::{SubsystemHandle, Toplevel};
#[async_trait]
trait SubsystemHandleTrait {
async fn on_shutdown_requested(&self);
}
#[async_trait]
impl SubsystemHandleTrait for SubsystemHandle {
async fn on_shutdown_requested(&self) {
SubsystemHandle::on_shutdown_requested(self).await;
}
}
struct MySubsystem {
counter: Arc<AtomicU32>,
}
impl MySubsystem {
pub async fn run(self, subsys: impl SubsystemHandleTrait) -> Result<(), String> {
self.counter.fetch_add(1, Ordering::Relaxed);
log::info!("Started.");
subsys.on_shutdown_requested().await;
self.counter.fetch_add(1, Ordering::Relaxed);
log::info!("Stopped.");
Ok(())
}
}
#[tokio::main]
async fn main() {
// Init logging
use env_logger::{Builder, Env};
Builder::from_env(Env::default().default_filter_or("debug")).init();
let counter = Arc::new(AtomicU32::new(0));
Toplevel::new()
.catch_signals()
.start("MySubsys", {
let counter = Arc::clone(&counter);
move |subsys| MySubsystem { counter }.run(subsys)
})
.handle_shutdown_requests(Duration::from_secs(2))
.await
.unwrap();
log::info!("Counter: {:?}", counter);
}
#[cfg(test)]
mod tests {
use super::*;
use core::future::Future;
use mockall::mock;
mock! {
MockedSubsystemHandle {}
impl SubsystemHandleTrait for MockedSubsystemHandle {
fn on_shutdown_requested<'b: 'a, 'a>(&'a self) -> impl Future<Output = ()> + core::marker::Send + 'a;
}
}
#[tokio::test]
async fn counter_gets_incremented_on_shutdown() {
// Arrange
let counter = Arc::new(AtomicU32::new(0));
let subsystem = {
let counter = Arc::clone(&counter);
MySubsystem { counter }
};
let (shutdown_sender, shutdown_receiver) = tokio::sync::oneshot::channel::<()>();
let mut subsys_handle = MockMockedSubsystemHandle::new();
subsys_handle
.expect_on_shutdown_requested()
.times(1)
.return_once(|| {
Box::pin(async {
shutdown_receiver.await.unwrap();
})
});
let running_subsystem = tokio::spawn(subsystem.run(subsys_handle));
tokio::time::sleep(Duration::from_millis(50)).await;
// Act & Assert
assert_eq!(counter.load(Ordering::Relaxed), 1);
shutdown_sender.send(()).unwrap();
tokio::time::sleep(Duration::from_millis(50)).await;
assert_eq!(counter.load(Ordering::Relaxed), 2);
running_subsystem.await.unwrap().unwrap();
}
}
|
@cemremengu Is this sufficient for you so I can close this issue? Or do you still require changes to the crate? |
Yes, thank you so much for this! Sorry, I was about the write reply but due to my internet connection I could only drop a heart :) Thanks again for your work. |
Hello I am new to Rust and sorry if this is a very obvious question.
I am using this crate with the hyper example and was wondering if you had any suggestions for testing approach when using
SubsystemHandle
as a parameter to the run function. How would you mock this parameter during tests so that it will not be necessary to callToplevel::new()
everytime?Thanks for your work!
The text was updated successfully, but these errors were encountered: