asomers / mockall

A powerful mock object library for Rust
Apache License 2.0
1.45k stars 61 forks source link

Mocking with tokio::sync::Mutex, confused for std::sync::Mutex #566

Closed brendano257 closed 4 months ago

brendano257 commented 5 months ago

Hi, I have a demo repo here where I have a trait that requires receiving a tokio::sync::Mutex as part of a type in a method. When trying to mock it out, I'm seeing a failure to compile like the below, which is telling me it was expecting a std::sync::Mutex instead of Tokio's. In my real implementation as well as the test, std::sync::Mutex is not use anywhere in the project so this is a very unexpected result. I also don't see any use of std::sync::Mutex in the mock! implementation code, so it doesn't seem like a macro hygiene issue?

Changing the mock to explicitly have tokio::sync::Mutex seems to resolve the lint issue, but then instantiating the mock in the test fails (and RustRover seems to think the mock doesn't event exist when trying to import it). This could simply be my lack of understanding of Mockall, since it's my first use-case for it. Am I missing something simple?

error[E0308]: mismatched types
   --> tests/mocks/mock_thing.rs:10:16
    |
6   | / mock! {
7   | |     pub MyThing {}
8   | |
9   | |     impl Thing for MyThing {
10  | |         fn new(shared: Arc<Mutex<String>>) -> Self;
    | |                ^^^^^^ expected `std::sync::Mutex<String>`, found `tokio::sync::Mutex<String>`
11  | |     }
12  | | }
    | |_- arguments to this method are incorrect
asomers commented 5 months ago

The first problem is because Mockall's generated code is using std::sync::Mutex. That's a defect and can be fixed. But the workaround is easy. The workaround is to explicitly us tokio::sync::Mutex just like you did. The other problem is probably caused by you having too many compile targets. You've got one target named lib. One integration test named test and an integration test named mod. You should reduce the number of compile targets. In fact, Mockall isn't normally useful in integration tests. It's normally useful in unit tests. If you want me to help you with that problem, you'll have to make the change and then post the actual error you get.

brendano257 commented 5 months ago

Thanks for the quick response! It actually looks like my IDE is failing to recognize the import pattern, but it compiles.

The only test looks like:

#[tokio::test]
async fn test_instantiating() {
    let mut mock = MockMyThing::new(Arc::new(Mutex::new("".to_string())));
}

and that panics with:

thread 'test_instantiating' panicked at tests/mocks/mock_thing.rs:5:1:
MockMyThing::new(Mutex { data: "" }): No matching expectation found

Now that I'm reading more closely this seems like expected behavior (since I didn't set an expectation on it), but how do you create mocks when there's already a new method implemented on the object being mocked?

asomers commented 5 months ago

From docs.rs:

One more thing: Mockall normally creates a zero-argument new method for every mock struct. But it won’t do that when mocking a struct that already has a method named new. The default method will still be present.
brendano257 commented 4 months ago

Ah, thanks and sorry about that.