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

Verify does not distinguish between a type and its implemented type in generic methods #660

Closed
Lizzy-Gallagher opened this issue Aug 21, 2018 · 3 comments

Comments

@Lizzy-Gallagher
Copy link

Normally, Verify usually can distinguish between types for generic method. However, when a type implements another, Verify logs that both types have been used. In the below example (using Moq 4.9), the line with Verify fails because it thinks that the method has been called with the type argument Default.

void Main()
{
	var mock = new Mock<MyService>();
	mock.Setup(m => m.DoSomething<Derived>());
	mock.Object.DoSomething<Derived>();
	
	mock.Verify(m => m.DoSomething<Default>(), Times.Never()); // Fails, it is called once
}

public class Default {}
public class Derived : Default {}

public class MyService
{
	public virtual void DoSomething<T>() {}
}
@stakx
Copy link
Contributor

stakx commented Aug 21, 2018

Hi @LizzyBradley and thank you for reporting this. On the surface, this looks like a bug. But I'm not sure that it actually is one. I'll post an example demonstrating why below, but let me first take a step back and describe the greater context of this issue.

We currently have two issues that are about the same general topic of how Moq should match method signatures against expectations. Interestingly, the two issues ask for opposing solutions:

  • This present issue asks for generic type arguments to be compared for equality instead of assignment compatibility. This issue focuses on the generic parameter list.

  • Unable to mock expression containing an anonymous type #656 asks for generic type arguments to be compared for assignment compatibility instead of equality. That issue focuses more on the return type.

I'm not sure which type comparison (exact equality vs. assignment compatibility) for which part of a method signature (return type vs. generic type parameter vs. regular parameter) in which situation (setup vs. verification) is the right combination that everyone will be happy with. I suspect that no matter whatever kind of signature matching we decide on, we're going to break someone else's code.

(Am I wrong in suspecting that there's no perfect solution to all of this? If someone has the answer—possibly derived from ECMA-335—that also won't break preexisting user code—or at least not too badly—I'd be happy to hear about it.)

Btw., the above scenario starts to look less wrong once the mocked method actually takes a parameter of the generic type parameter. Take this slightly altered example, where I just renamed Default to Animal, Derived to Horse, DoSomething to Feed, and added a parameter to the mocked method:

public class Animal { }
public class Horse : Animal { }

public interface IFeeder
{
	void Feed<T>(T x) { }
}

var mock = new Mock<IFeeder>();
mock.Object.Feed(new Horse());
mock.Verify(m => m.Feed(It.IsAny<Animal>()), Times.Never());

If you're anything like me, you'd probably expect verification failure to happen here, since we did feed an animal, after all.

@stakx
Copy link
Contributor

stakx commented Aug 27, 2018

@LizzyBradley - given the above feedback, how do you think we should proceed with this issue? (If there's no further response, I'll close this issue in about 3-4 days' time.)

@stakx
Copy link
Contributor

stakx commented Sep 5, 2019

@Lizzy-Gallagher - In Moq 4.13.0, you can now use type matchers to get your test to pass. First, define a type matcher that matches an exact type:

[TypeMatcher]
public class IsExactType<T> : ITypeMatcher
{
    public bool Matches(Type typeArgument) => typeArgument == typeof(T);
}

Then change your call to Verify as follows:

-mock.Verify(m => m.DoSomething<            Default >(), Times.Never()); // Fails, it is called once
+mock.Verify(m => m.DoSomething<IsExactType<Default>>(), Times.Never()); // Passes

@stakx stakx removed the unresolved label Sep 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants