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

How to mock multiple impls of generic struct instances? #271

Open
ChriFo opened this issue Apr 18, 2021 · 12 comments
Open

How to mock multiple impls of generic struct instances? #271

ChriFo opened this issue Apr 18, 2021 · 12 comments
Labels
question Usage question

Comments

@ChriFo
Copy link

ChriFo commented Apr 18, 2021

First of all: Thank you for a very useful library.

I have this (simplified) data structure:

struct Envelope<T> {
    content: T,
}

impl From<A> for Envelope<A> {
    fn from(a: A) -> Self {
        /* */
    }
}

impl From<B> for Envelope<C> {
    fn from(b: B) -> Self {
        /* */
    }
}

impl Envelope<A> {
    fn send(e: E, f: F) -> Result<D> {
        /* */
    }
}

impl Envelope<C> {
    fn send(e: E) -> Result<D> {
        /* */
    }
}

And I use is it like that:

Envelope::from(a).send(e, f).await;
Envelope::from(b).send(e).await;

When trying to mock it like that:

cfg_if! {
    if #[cfg(test)] {
        use futures::Future;
        use mockall::*;

        mock! {
            pub Envelope<T: 'static> {}

            impl From<A> for Envelope {
                pub fn from<V: 'static>(value: V) -> MockEnvelopeA;
            }

            impl From<B> for Envelope {
                pub fn from<V: 'static>(value: V) -> MockEnvelopeB;
            }
        }

        mock! {
            pub EnvelopeA {
                pub fn send(&mut self, e: E, f: F) -> impl Future<Output = Result<D>>;
            }
        }

        mock! {
            pub EnvelopeB {
                pub fn send(&mut self, e: F) -> impl Future<Output = Result<D>>;
            }
        }

        use MockEnvelope as Envelope;
    } else {
        use Envelope;
    }
}

The following error appears:

error[E0592]: duplicate definitions with name `from_context`.

Thank you for any helpful information.

@asomers asomers added the question Usage question label Apr 18, 2021
@asomers
Copy link
Owner

asomers commented Apr 18, 2021

I think your usage is basically correct, but there are a couple of small problems:

  • In your first mock! block, your from definition doesn't match the real struct. The Envelope type should be generic, like impl From<A> for Envelope<A>, and the method itself should not be generic. Or did you get a different error when you tried that?
  • The mock definition of send doesn't match the real struct. The real definition has no self parameter, and returns an immediate value, not a Future.

Those two problems might not be cause of your compile failure, but first things first. What happens when you make the struct match the mock?

@ChriFo
Copy link
Author

ChriFo commented Apr 19, 2021

Thats right: first things first!

  1. Adjusted first mock block looks now like that:
mock! {
    pub Envelope<T: 'static> {}

    impl From<A> for Envelope<A> {
        pub fn from(value: A) -> MockEnvelopeA;
    }

    impl From<B> for Envelope<B> {
        pub fn from(value: B) -> MockEnvelopeB;
    }
}
  1. The impls should be like this:
impl Envelope<A> {
    fn send(&'_ mut self, e: E, f: F) -> Result<D> {
        /* */
    }
}

impl Envelope<C> {
    fn send(&'_ mut self, e: E) -> Result<D> {
        /* */
    }
}

and therefor the mocks are now:

mock! {
    pub EnvelopeA {
        pub fn send<'a>(&'a mut self, e: E, f: F) -> impl Future<Output = Result<D>>;
   }
}
mock! {
    pub EnvelopeB {
        pub fn send<'a>(&'a mut self, e: E) -> impl Future<Output = Result<D>>;
    }
}

With these changes I get the following errors:

  1. For the first mock!:
    error[E0119]: conflicting implementations of trait `std::default::Default` for type `MockEnvelope_From<_>`
    error[E0428]: the name `MockEnvelope_From` is defined multiple times
  2. For the struct definition:
    error[E0592]: duplicate definitions with name `from_context`
  3. For the whole first mock! block:
    error[E0592]: duplicate definitions with name `checkpoint`

The reason for my problem are the different method signatures of send because of different implementations in different Envelope types. The mocks MockEnvelopeA and MockEnvelopeB are just hacks to have something like a switch for the different send methods. 🤔

@ChriFo
Copy link
Author

ChriFo commented Apr 23, 2021

According to this line the syn crate is used to parse anything given to mock!. The result is returned into this struct which impls some traits from the syn crate as well.

So is the problem describes above a limitation of syn, @asomers?

@asomers
Copy link
Owner

asomers commented Apr 23, 2021

No, syn is just fine. The main problem, as you observed, is that mockall generates some non-generic code for every mocked function. That means that multiple mock impls of the same function (even if they have different generic parameters) cause name conflicts. So you have multiple problems:

  • You can't mock from multiple times on the same mock structure, because of said name conflicts.
  • You want send to have a different signature depending on the struct's type parameter. But Mockall can't do that for the same reason; you can only mock each method once.
  • It's not clear from your post, but perhaps you need the calling code to be generic over all Envelope types? I don't see how that would be possible, though, since send can have different signatures.

So here are some possibilities:

  • Abandon struct generics. Just create EnvelopeA and EnvelopeB types, and mock each one like usual. That would prevent calling code from being generic over all struct types, though.
  • If you can change send's signature to be consistent, then you can have a single mock!{ pub Envelope...} that includes the send method. But you'll have to manually implement from. You can do that just by impl From<A> for MockEnvelope{...}.
  • If you aren't picky about the name of the from method, you could create two separate methods from_a and from_b. Those you could mock like usual.
  • If you don't want to change your signatures at all, you'll have to do the mocking at least partially by hand. You can do it by hand-writing a wrapper struct that wraps a Mockall object. That way you can still set expectations just like normal. It would look something like this:
mock!{
    pub InnerEnvelope {
        fn from_a(a: A) -> Self;
        fn from_b(b: B) -> Self;
        fn send_a(&self, e: E, f: F) -> impl Future<Output = Result<D>>;
        fn send_b(&self, e: E) -> impl Future<Output = Result<D>>;
    }
}
struct MockEnvelope<T> {
    inner: MockInnerEnvelope,
    t: std::marker::PhantomData<T>
}
impl From<A> for MockEnvelope<A> {
    pub fn from(a: A) -> MockEnvelope {
        Self {
            inner: MockInnerEnvelope::from_a(a),
            t: PhantomData
         }
    }
}
impl From<B> for MockEnvelope<B> {
    pub fn from(b: B) -> MockEnvelope {
        Self {
            inner: MockInnerEnvelope::from_b(b),
            t: PhantomData
         }
    }
}
impl MockEnvelope<A> {
    pub fn send(&mut self, e: E, f: F) -> impl Future<Output = Result<D>> {
        self.inner.send_a(e, f)
    }
}
impl MockEnvelope<B> {
    pub fn send(&mut self, e: E) -> impl Future<Output = Result<D>> {
        self.inner.send_b(e)
    }
}

@ChriFo
Copy link
Author

ChriFo commented Apr 23, 2021

I take option 4 and that compiles without errors.
Awesome! Thank you!

@ChriFo ChriFo closed this as completed Apr 23, 2021
asomers added a commit that referenced this issue Apr 24, 2021
A specific impl is an implementation of a trait on a generic struct with
specific generic parameters, like `impl Foo for Bar<i32> {}`

Issue #271
asomers added a commit that referenced this issue Apr 24, 2021
A specific impl is an implementation of a trait on a generic struct with
specific generic parameters, like `impl Foo for Bar<i32> {}`

Issue #271
asomers added a commit that referenced this issue Apr 24, 2021
A specific impl is an implementation of a trait on a generic struct with
specific generic parameters, like `impl Foo for Bar<i32> {}`

Issue #271
@asomers
Copy link
Owner

asomers commented Apr 24, 2021

@ChriFo I fixed the problem with implementing the same trait multiple times on a generic struct. You should be able to just use plain mock! now, with no fancy hand-rolled mocks. Try it out!

@ChriFo
Copy link
Author

ChriFo commented Apr 24, 2021

I changed dev dependency to:

mockall = { git = "https://github.com/asomers/mockall.git", branch = "specific_impl" }

This is now the output of RUSTFLAGS="-Z macro-backtrace" cargo +nightly test:

Bildschirmfoto 2021-04-24 um 20 18 57

@ChriFo ChriFo reopened this Apr 24, 2021
@asomers
Copy link
Owner

asomers commented Apr 25, 2021

Well, that's because From<A> and From<B> are different traits. PR #274 only solves the problem of implementing exactly the same trait twice. I think I'll fix that too, but in a separate PR.

@asomers
Copy link
Owner

asomers commented Jul 30, 2021

@ChriFo I failed to follow up on this issue. Does your application work with the latest Mockall? I think #275 might've solved your other problem.

@ChriFo
Copy link
Author

ChriFo commented Aug 18, 2021

Thanks for try to solve my problems ;-) My application works with the current latest version.
But when changing the code back to my old problem:

use futures::Future;
use mockall::*;

mock! {
    pub Envelope<T: 'static> {
        fn fake_send(&self) -> impl Future<Output = Result<HttpResponse>>;
    }

    impl<HarborHook> From<(HarborHook, Option<(String, usize)>)> for Envelope<HarborHook> {
        pub fn from(value: (HarborHook, Option<(String, usize)>)) -> Envelope<HarborHook>;
    }

    impl<Value> From<Value> for Envelope<Value> {
        pub fn from(value: Value) -> Envelope<Value>;
    }
}

impl MockEnvelope<HarborHook>  {
    fn send(&self, info: String, client: &Client, hash: String, config: &Config) -> impl Future<Output = Result<HttpResponse>> {
        self.fake_send()
    }
}

impl MockEnvelope<Value> {
    pub fn send(&self, info: String, client: &Client, db: &Pool, config: &Config) -> impl Future<Output = Result<HttpResponse>> {
        self.fake_send()
    }
}

I get:

duplicate definitions with name `from_context`
duplicate definitions for `from_context` (rustcE0592)

@asomers
Copy link
Owner

asomers commented Aug 18, 2021

Yep, not surprising. Each of those from methods creates a from context. In your case, you could easily implement From manually. But it would be nice to fix this problem more generally. Do you have any thoughts on what syntax Mockall should use to handle cases like this?

@ChriFo
Copy link
Author

ChriFo commented Aug 18, 2021

Maybe something like a macro, e.g., mock_context or mock_impl.

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