asomers / mockall

A powerful mock object library for Rust
Apache License 2.0
1.5k stars 62 forks source link

Skip Generating mock methods #471

Closed sadokmtir closed 1 year ago

sadokmtir commented 1 year ago

It would be nice to skip the generation for some methods marked with a Macro attribute for example.

In my uses case, I have a struct that it is auto-mocked using mockall and I use some fake method in its struct to genrate a test double for my unit tests. Here is an example:

pub struct APIAccess {
    pub key: String,
}

#[cfg_attr(test, automock)]
impl APIAccess {

    #[cfg(test)]
    pub fn fake() -> APIAccess {
        APIAccess {
            key: Faker.fake::<String>(), // or simply key: "sample_key"
        }
    }
    pub fn is_admin(&self) -> bool {
        false
    }
}

I would like to be able in my test to have expectation for the method is_admin and also be able to use some fake data for the Struct.

        async fn test_list() {
            mock_api_permission_service // this is another struct being mocked
                .expect_list()
                .returning(|| {
                    let api_access_0 = APIAccess::fake(); // it panics at this level

                    let api_access_1 = APIAccess::fake();

                    let xs = vec![
                        api_access_0,
                        api_access_1,
                    ];  

                    Ok(xs)
                });

            let mut mock_api_access = APIAccess::new();

            mock_api_access
                .expect_is_admin()
                .times(1)
                .returning(|| true);
        }

On the line where I call APIAccess::fake(), my test panics with the message: No matching expectation found. Because I am getting an automatic mock implementation for it.

PS: I already checked this issue https://github.com/asomers/mockall/issues/242 and I don't see how the proposed solution could solve my problem unfortunately.

asomers commented 1 year ago

Your problem is worse than simply wanting to opt-out of mocking one particular method. Your real problem is that you're trying to mix real objects and mock objects in the same code. Your definition for fake tries to create a real APIAccess object, but you're calling it in a a context that expects MockAPIAccess. You need to decide which object type you want, and stick with it.

sadokmtir commented 1 year ago

That is just a static method that the mockall mocks by default and there is no way to workaround it besides importing the APIAccess and MockAPIAccess separately. If there is a way how to skip mocking a specific function that would solve the problem here. Also not that this is just the test function not the application code. The uses case being tested requires both objects and Mocks. For example: getting the mock object from the request context and having a service that loads the objects from external resource. In this case you need both, you need a mock object for the object deducted from the context and you need an object double to return them when you mock the service call. I see this as very common uses case. So, I really don't get why I need to stick with one of them.

asomers commented 1 year ago

If there is a way how to skip mocking a specific function that would solve the problem here

Yes there is such a way, but no it won't solve your problem.

The uses case being tested requires both objects and Mocks

You cannot mix real objects and mock objects in the same code. The real object is replaced at compile-time with a different object. The mock object likely has a different size and definitely has different methods. It is not a trait object, that can be swapped in at runtime. In this case, you've clearly defined MockApiPermissionService::list as returning a vec of MockApiAccess. So there's no possible way for it to return a real ApiAccess.

It's normal that you want to have tests for real ApiAccess objects, and also tests that make use of MockApiAccess. But you've got to separate them at compile-time. Any struct defined in terms of one cannot work with the other.

sadokmtir commented 1 year ago

Yes there is such a way, but no it won't solve your problem.

What is the way if we have a struct with some function that I do not want to mock ? Apart from this example, I always have the need in other examples, where the struct has several methods that I don't want to mock all of them. The workaround you presented here https://github.com/asomers/mockall/issues/242#issuecomment-757541247 does not solve the issue as there is no Trait involved.

asomers commented 1 year ago

You can do it like this:

struct Foo {}
#[automock]
impl Foo {
    fn foo(&self){...}
}
impl Foo {
    fn bar(&self){...}
}

In this way, the foo method will be mocked, but bar will not.

sadokmtir commented 1 year ago

Thanks but in my case I cannot use the #[automock] instead I am using the mock!() macro. How could I do it in that case ?

asomers commented 1 year ago

That's even easier. Just don't put bar in the mock! invocation.

asomers commented 1 year ago

Closing on the assumption that the OP's concerns have been adequately answered.