Skip to content
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

Support mockable use of structs without changing the type #479

Closed
Ben-PH opened this issue Apr 14, 2023 · 2 comments
Closed

Support mockable use of structs without changing the type #479

Ben-PH opened this issue Apr 14, 2023 · 2 comments
Labels
question Usage question

Comments

@Ben-PH
Copy link

Ben-PH commented Apr 14, 2023

Related issue: #331

Related discussion: #478

When testing a function with a mocked implementation passed in, it must be a trait by virtue of the fact that a mocked struct is seen as a different type. This is problematic in the context where you do not wish to make the function generic on that input.

I'm not 100% sure what a good solution will look like, but here is a a rough first draft:

// We want to test this function, but we want to mock foo in different ways
fn uses_foo(foo: Foo) -> u32 {
    foo.do_bar()
}

#[test]
fn test_with_mocked_foo() {
    // provides a Foo which can be used in the same way as a `MockFoo` generated from `automock` applied to a `Foo` struct
    let mut mocked_foo = mockall::use_mocked!(Foo::default());

    mocked_foo.expect_do_bar().returning(|| 42);

    let expect_42 = uses_foo(mocked_foo);
}

// this is the current solution:
// T is needed in order to be able to test this function with a mocked: Would prefer to just use a Foo instead of generics
fn uses_foo_trait<T: FooTrait>(foo: T) -> u32 {
    foo.do_bar()
}

#[mockall::automock]
trait FooTrait {
    fn do_bar(&self) -> u32;
}

#[test]
fn test_with_mocked_foo() {
    // provides a Foo which can be used in the same way as a `MockFoo` generated from `automock` applied to a `Foo` struct
    let mut mocked_foo = MockFooTrait::new();

    mocked_foo.expect_do_bar().returning(|| 42);

    let expect_42 = uses_foo_trait(mocked_foo);
}

// How this might be made to work:
mockall::struct_mock! {
    struct Foo {
        bar: Bar,

        // if Foo is being mocked:
        // - adds this field
        // - redefines the structs implementation to always use `self.mock_foo` instead of `self`
        #[mockall::struct_internal]
        mock_foo: MockFoo,
    }


    // Generatios MockFoo similar to `mockall::automock`, and also redefines implementations to use `self.mock_foo`
    #[mockall::automock_struct_internal] // could use `mockall::mock_struct_internal!` instead
    impl Foo {
        fn do_bar(&self) -> u32 {
            self.bar.do_bar()
        }
    }

    // hypothetically would expand to something like....
    impl Foo {
        fn do_bar(&self) -> u32 {
            self.mocked_self.do_bar()
        }

        // and the expansions equivilent to `mockall::automock`
        fn expect_do_bar(...) {
            self.mocked_self.expect_do_bar(...)

        }
    }
}
@asomers
Copy link
Owner

asomers commented Apr 14, 2023

This is not how Rust works. You cannot create a "Foo" that can be used in the same way as a "MockFoo". Rust is a strongly typed, statically typed language. A variable is either a Foo or a MockFoo, not both. The solution to your problem is to use #[mockall_double] correctly. Or, if for some reason you can't, then you must straighten out all of your cfg directives to ensure that you never pass a MockFoo to a function expecting a Foo or vice versa.

@asomers asomers added the question Usage question label Apr 14, 2023
@asomers
Copy link
Owner

asomers commented Apr 16, 2023

Closing. We discussed the issue in the linked discussion.

@asomers asomers closed this as completed Apr 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Usage question
Projects
None yet
Development

No branches or pull requests

2 participants