mockall icon indicating copy to clipboard operation
mockall copied to clipboard

How to mock multiple impls of generic struct instances?

Open ChriFo opened this issue 4 years ago • 12 comments

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.

ChriFo avatar Apr 18 '21 21:04 ChriFo

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?

asomers avatar Apr 18 '21 21:04 asomers

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 avatar Apr 19 '21 08:04 ChriFo

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?

ChriFo avatar Apr 23 '21 14:04 ChriFo

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)
    }
}

asomers avatar Apr 23 '21 14:04 asomers

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

ChriFo avatar Apr 23 '21 15:04 ChriFo

@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!

asomers avatar Apr 24 '21 03:04 asomers

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 avatar Apr 24 '21 06:04 ChriFo

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 avatar Apr 25 '21 22:04 asomers

@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.

asomers avatar Jul 30 '21 22:07 asomers

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)

ChriFo avatar Aug 18 '21 10:08 ChriFo

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?

asomers avatar Aug 18 '21 13:08 asomers

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

ChriFo avatar Aug 18 '21 14:08 ChriFo