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 fails to recognize subclass as type argument for generic method when mock invoked in supporting method #151

Closed
SimonBell opened this issue Feb 3, 2015 · 2 comments

Comments

@SimonBell
Copy link

SimonBell commented Feb 3, 2015

When a mocked interface is passed as an argument and a generic method is invoked, the Verify call does not recognize the correct type arguments if a subclass is supplied as the type argument. The Verify call successfully recognizes the subclass type argument if the mock is invoked directly rather than in a supporting method.

The expected behaviour is that the 3 tests below pass. The actual behaviour is that only the first test passes. All 3 tests can be made to pass by changing the type argument in Verify to the base class rather than the subclass:

mock.Verify(m => m.Dispatch(It.IsAny<Message>()), Times.Once);

but in the final 2 tests, the Verify calls fail to match the subclass type for the generic method invocation.

[TestClass]
public class MoqTest
{
    public abstract class Message                   {}
    public sealed class ConcreteMessage : Message   {}

    public interface IMessageHandler<in T> where T : Message
    {
        void Handle(T @event);
    }

    public interface IMessageDispatcher 
    {
        void Dispatch<T>(T @event) where T : Message;
    }

    public static class FactoryMethodMessageRaiser
    {
        public static Func<IMessageDispatcher> FactoryMethod;

        public static void Raise(Message @event)
        {
            FactoryMethod.Invoke().Dispatch(@event);
        }
    }

    public static class StaticMethodMessageRaiser
    {
        public static void Raise(IMessageDispatcher dispatcher, Message @event)
        {
            dispatcher.Dispatch(@event);
        }
    }

    [TestMethod]
    public void DispatchDirectlyOnMockVerifyPasses()
    {
        var mock = new Mock<IMessageDispatcher>();
        mock.Object.Dispatch(new ConcreteMessage());
        mock.Verify(m => m.Dispatch(It.IsAny<ConcreteMessage>()), Times.Once);
    }

    [TestMethod]
    public void FactoryMethodDomainEventRaiserVerifyFails()
    {
        var mock = new Mock<IMessageDispatcher>();
        FactoryMethodMessageRaiser.FactoryMethod = () => mock.Object;

        FactoryMethodMessageRaiser.Raise(new ConcreteMessage());
        mock.Verify(m => m.Dispatch(It.IsAny<ConcreteMessage>()), Times.Once);
    }

    [TestMethod]
    public void StaticMethodDomainEventRaiserVerifyFails()
    {
        var mock = new Mock<IMessageDispatcher>();
        StaticMethodMessageRaiser.Raise(mock.Object, new ConcreteMessage());
        mock.Verify(m => m.Dispatch(It.IsAny<ConcreteMessage>()), Times.Once);
    }
}
@stakx
Copy link
Contributor

stakx commented Jun 22, 2017

@SimonBell. I am a little late to the party, but I guess someone still owes you an answer of some sort, so here goes...

I think your example essentially boils down to this:

public abstract class Message { }
public sealed class ConcreteMessage : Message { }

public interface IMessageDispatcher
{
    void Dispatch<T>(T message) where T : Message;
}

var mock = new Mock<IMessageDispatcher>();
mock.Object.Dispatch((Message)new ConcreteMessage());
mock.Verify(m => m.Dispatch(It.IsAny<ConcreteMessage>()), Times.Once);

Note the downcast (Message). This is what your Raiser classes do. Because of it, you're calling the method Dispatch<Message>. However, you are in effect verifying a different method, namely Dispatch<ConcreteMessage>, due to type inference from It.IsAny<ConcreteMessage>().

So what you can do is to change the Verify to:

mock.Verify(m => m.Dispatch<Message>(It.IsAny<ConcreteMessage>()), Times.Once);
//                          ^^^^^^^

Would this work for you?

Beyond this suggestion, I haven't really thought about what can or should be done to support your scenario. Do you (or anyone else) feel we should follow this up any further? If not, I will close this issue in approx. 2 or 3 weeks.

@kzu
Copy link
Member

kzu commented Jun 22, 2017

I think the way it currently works makes sense. There is a mismatch between the actual invocation and the verification.

@kzu kzu closed this as completed Jun 22, 2017
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

3 participants