Search code examples
c#moq

Using Moq with a template method


I am trying to Moq a Rabbit MQ method.

ValueTask BasicPublishAsync<TProperties>(string exchange, string routingKey,
    bool mandatory, TProperties basicProperties, ReadOnlyMemory<byte> body,
    CancellationToken cancellationToken = default)
    where TProperties : IReadOnlyBasicProperties, IAmqpHeader;

What is the correct form of:

mockRabbitModel.Setup(a => a.BasicPublishAsync<It.IsSubtype<IAmqpHeader>>(
    It.IsAny<string>() /*exchange*/,
    It.IsAny<string>() /*routingKey*/,
    It.IsAny<bool>() /* mandatory*/,
    // ??????,
    It.Is<ReadOnlyMemory<byte>>(b =>
        this.TestSentMessage(b, (expected, actual) => 
            Assert.AreEqual(expected, actual.Command))),
    It.IsAny<CancellationToken>()));

Moq can't work with TProperties directly and I have seen various ideas for mapping the argument with ??????


Solution

  • As you have used It.IsSubtype<IAmqpHeader> as the generic parameter for the BasicPublishAsync, you should use the same type for the basicProperties wrapped into a It.IsAny.

    Here is a simplified version of your code:

    public interface IFoo
    {
        ValueTask BasicPublishAsync<TProperties>(string key1, TProperties properties);
    }
    

    Then the mocking should be done like this:

    var foo_mock = new Mock<IFoo>();
    foo_mock.Setup(foo => foo.BasicPublishAsync<It.IsSubtype<int>>(It.IsAny<string>(), It.IsAny<It.IsSubtype<int>>()))
            .Returns(() => ValueTask.FromException(new Exception("BlahBlah")));
    

    Here you can find a working sample code: https://dotnetfiddle.net/zSEE5D


    UPDATE #1

    To tackle the multi interface constraint you can introduce a custom TypeMatcher:

    The interfaces:

    public interface IFoo
    {
        ValueTask BasicPublishAsync<TProperties>(string key1, TProperties properties) where TProperties: IA, IB;
    }
    
    
    public interface IA {}
    public interface IB {}
    internal interface IAB: IA, IB {} // just for testing
    

    The type matcher

    [TypeMatcher]
    public class FooTypeMatcher<T> : IA, IB, ITypeMatcher
        where T : IA, IB
    {
        bool ITypeMatcher.Matches(Type typeArgument)
        {
            return typeof(T).IsAssignableFrom(typeArgument);
        }
    }
    

    The mock setup:

    var foo_mock = new Mock<IFoo>();
    foo_mock.Setup(foo => foo.BasicPublishAsync<FooTypeMatcher<IAB>>(It.IsAny<string>(), It.IsAny<FooTypeMatcher<IAB>>()))
            .Returns(() => ValueTask.FromException(new Exception("BlahBlah")));
    

    Here you can find a working sample code: https://dotnetfiddle.net/5kOz8W