I have some tests, that all look similar:
#[test]
fn some_test() {
// create system under test
let sut = SomeService {};
// do some setup
sut.setup();
let result = sut.some_op("foo".to_string(), 42);
assert_eq!(true, result);
}
I want to reduce boilerplate, so I would like to create a helper/wrapper, that I will use like this:
#[test]
fn some_test() {
test_helper(|operation| {
let result = operation("foo".to_string(), 42);
assert_eq!(true, result);
});
}
To do so, I need to create a fuction accepting closure, which itself also accepts closure.
I've struggled a bit with it, until i discovered this post.
So my implementation now looks like this and works:
pub fn test_helper<T>(test_body: T) -> ()
where
for<'a> T: FnOnce(&'a dyn Fn(String, i32) -> bool) -> (),
{
let sut = SomeService {};
sut.setup();
test_body(&|s, i| sut.some_op(s, i))
}
#[test]
fn some_test() {
test_helper(|operation| {
let result = operation("foo".to_string(), 42);
assert_eq!(true, result);
});
}
But now the real problem - how do I make it all async?
I tried to do this using generics and constraints:
pub async fn test_helper<T, Fut1, Fut2>(test_body: T) -> ()
where
for<'a> T: FnOnce(&'a dyn Fn(String, i32) -> Fut1) -> Fut2,
Fut1: Future<Output = bool>,
Fut2: Future<Output = ()>,
{
let sut = SomeService {};
sut.setup();
test_body(&|s, i| -> Fut1 { sut.some_op_async(s, i) }).await;
}
It does not compile with error
error[E0308]: mismatched types
--> src/main.rs:55:37
|
11 | async fn some_op_async(&self, a1: String, a2: i32) -> bool {
| ---- the `Output` of this `async fn`'s found opaque type
...
46 | pub async fn test_helper<T, Fut1, Fut2>(test_body: T) -> ()
| ---- this type parameter
...
55 | test_body(&|s, i| -> Fut1 { sut.some_op_async(s, i) }).await;
| ^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `Fut1`, found opaque type
|
= note: expected type parameter `Fut1`
found opaque type `impl futures::Future`
= help: type parameters must be constrained to match other types
If I understand Rust generics correctly, it means that type parameter Fut1
will be specified by the caller, and it may very well not match the concrete implementation that I use inside my function.
I also randomly tried using trait objects, Box
'es, and other stuff that I've seen during my short experience with Rust.
Can you please help me fix my code?