How to mock multiple impls of generic struct instances?
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.
I think your usage is basically correct, but there are a couple of small problems:
- In your first
mock!block, yourfromdefinition doesn't match the real struct. The Envelope type should be generic, likeimpl 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
senddoesn't match the real struct. The real definition has noselfparameter, and returns an immediate value, not aFuture.
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?
Thats right: first things first!
- 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;
}
}
- 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:
- 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 - For the struct definition:
error[E0592]: duplicate definitions with name `from_context` - 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. 🤔
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?
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
frommultiple times on the same mock structure, because of said name conflicts. - You want
sendto 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
sendcan 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 singlemock!{ pub Envelope...}that includes thesendmethod. But you'll have to manually implementfrom. You can do that just byimpl From<A> for MockEnvelope{...}. - If you aren't picky about the name of the
frommethod, you could create two separate methodsfrom_aandfrom_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)
}
}
I take option 4 and that compiles without errors. Awesome! Thank you!
@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!
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:
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.
@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.
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)
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?
Maybe something like a macro, e.g., mock_context or mock_impl.