Search code examples
c#unit-testingmoq

Mocked method with optional parameters returns unexpected different values depending on optional defaults


I mock a method from an interface which has optional parameters to return true.
However, depending on whether I pass all the optional parameters to the call or only the required parameters I get a different result.

Here's a full working example of the behavior:

using Moq;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
namespace MoqTest
{
    public interface IProcedureService
    {
        Task<bool> InterfaceMethod(string req1, int req2, int? opt1 = null, CancellationToken opt2 = default);
    }
    public class Tests
    {
        Mock<IProcedureService> _procedureService = new Mock<IProcedureService>();
        ITestOutputHelper _output;
        public Tests(ITestOutputHelper output) => _output = output;

        [Xunit.Fact]
        public async void DoTheTest()
        {
            _procedureService.Setup(x => x.InterfaceMethod(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
                .ReturnsAsync(true);

            _output.WriteLine($"result from IsUniqueProcedureNameInWorkspace (using optional defaults) " +
                $"{(await _procedureService.Object.InterfaceMethod("", 0))}"); // returns false
            _output.WriteLine($"result from IsUniqueProcedureNameInWorkspace (NOT using optional defaults) " +
                $"{(await _procedureService.Object.InterfaceMethod("", 0, 0, default))}"); // returns true
        }
    }
}

This outputs the following:

Standard Output: 
result from IsUniqueProcedureNameInWorkspace (using optional defaults) False
result from IsUniqueProcedureNameInWorkspace (NOT using optional defaults) True

I would expect them to both return True as I set It.IsAny for all the parameters in the Setup.
What am I missing here?

My Moq version is 4.20.70


Solution

  • It is indeed as Jon Skeet already suggested, but since I had the thought already while reading through the question (so before seeing his comment), and since I've also gone to the trouble of reproducing the issue, I take the liberty to write the following as an answer rather than a comment.

    Instead of relying on output, which as to be inspected, I offer the solution as a test that uses assertions to demonstrate that the fix works:

    [Fact]
    public async Task FixWithAssertions()
    {
        _procedureService
            .Setup(x => x.InterfaceMethod(
                It.IsAny<string>(),
                It.IsAny<int>(),
                It.IsAny<int?>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);
    
        var result1 = await _procedureService.Object.InterfaceMethod("", 0);
        var result2 = await _procedureService.Object.InterfaceMethod("", 0, 0, default);
    
        Assert.True(result1);
        Assert.True(result2);
    }
    

    This test passes (both assertions).

    The only thing I chaged in Setup was exactly that I've indicated It.IsAny<int?>() rather than It.IsAny<int>(). As Jon wrote, those aren't the same type, which may explain why the Setup doesn't capture the call that omits the optional values.