Search code examples
unit-testingasynchronousrustmockingrust-tokio

How to make tokio test fail if thread panicked?


I have a struct with implementation that uses automock from mockall crate:

pub struct MyStruct {...}

#[cfg_attr(test, automock)] 
impl MyStruct {
...
    pub async fn my_struct_function() {...}
...
}

I need to test that my_struct_function is called exactly once by another async method:

Update: I expressed the problem in a poor manner. If other_async_method calls my_struct_function it's fine, they both run in the same task. But in my case other_async_method spawns a new task and my_struct_function is called in this new task. This is why the main task of the test doesn't "see" the panic and succeeds.

#[tokio::test]
async fn my_test() {
...
    let mut my_struct_mock = MockMyStruct::default();
    my_struct_mock.
        .expect_my_struct_function()
        .times(1)
        .returning(...);

    let result: Result<...> = OtherStruct::init_method(..., my_struct_mock, ...)
        .other_async_method()  // updated: spawns a new task that calls MockMyStruct::my_struct_function
        .await;
...
}

At the moment if I change the times(1) method to some other value N the test is still succesfull, because the code panics in a tokio thread, and not the main test thread. I can verify it by running the test with nocapture option:

$ cargo test my_test -- --nocapture

thread '...tests::test_my_test' panicked at 'MockMyStruct::my_struct_function: Expectation(<anything>) called 1 time(s) which is fewer than expected N'

How can I make the test fail when there is a discrepancy between the expected and the actual number of times the function is called?

I am a novice to Rust and wasn't able to find a solution sound by searching online. But I expect that there is a solution at the library level (mockall of tokio test) and not a "hack".


Solution

  • I opened an issue in the mockall GitHub repository: https://github.com/asomers/mockall/issues/461

    The owner asomers suggested to use the checkpoint method and it solved my problem:

    you can use mockedObject.checkpoint() to verify that all of its expectations have been satisfied prior to dropping the object.

    #[tokio::test]
    async fn my_test() {
    ...
        let mut my_struct_mock = MockMyStruct::default();
        my_struct_mock.
            .expect_my_struct_function()
            .times(1)
            .returning(...);
    
        let result: Result<...> = OtherStruct::init_method(..., my_struct_mock, ...)
            .other_async_method()  // updated: spawns a new task that calls MockMyStruct::my_struct_function
            .await;
    
        my_struct_mock.checkpoint();
    ...
    }