简体   繁体   中英

Moq - Method evaluation with nullable parameter throws null exception

I know that I can new up an object and pass it in but I'm trying to match the syntax and patterns since the value is never actually used.

Implementation:

public interface IDemoProvider
{
    string GetStringById(int? id);
}

public interface IDemoService
{
    bool Validate(ServiceRequest request);
}

public class ServiceRequest
{
    public string foo { get; set; }
    public int? bar { get; set; }
}

public class SomeDemoProvider : IDemoProvider
{
    public virtual string GetStringById(int? id)
    {
        if (id != null)
            return "success";
        else
            return "else";
    }
}

public class BaseDemoService : IDemoService
{
    public IDemoProvider Provider { get; set; }

    public virtual bool Validate(ServiceRequest request)
    {
        if (request.bar == null)
            return false;

        return true;
    }
}

public class SomeDemoService : BaseDemoService
{
    public SomeDemoService(IDemoProvider provider)
    {
        Provider = provider;
    }

    public virtual string Post(ServiceRequest request)
    {
        if (!Validate(request))
            throw new Exception("Invalid Request");
        return Provider.GetStringById(request.bar);
    }
}

Test Code:

[TestFixture]
public class ProgramTests
{
    [Test]
    public void SomeDemoServiceTest()
    {
        var mockProvider = new Mock<IDemoProvider>();
        mockProvider.Setup(p => p.GetStringById(It.IsAny<int?>())).Returns(() => "success");

        object[] args = { mockProvider.Object };

        var mockService = new Mock<SomeDemoService>(args) {CallBase = true};
        mockService.Setup(s => s.Validate(It.IsAny<ServiceRequest>())).Returns(true);

        string result = mockService.Object.Post(It.IsAny<ServiceRequest>());

        Assert.AreEqual("success", result);
    }
}

Issue

This results in a NullReferenceException which makes sense since it's trying to resolve request.Id while running which is null at the request level.

However, if I replace the parameter with null , it works fine:

return Provider.GetStringById(null);

My question here is whether there's some way to make the interceptor recognize before evaluating the expression that it should just return the value instead of trying to evaluate the expression.

I could mock out the rest of the request object but it seems silly since the values are never used according to the test. The real question here is whether I should be writing the code or the test differently to avoid this scenario or if I'm over complicating an issue with a simple workaround.

You are testing it wrong.

If you are trying to exercise the method under test then this line...

string result = mockService.Object.Post(It.IsAny<ServiceRequest>());

is being used incorrectly. You would use It.IsAny<ServiceRequest>() as part of the setup. In the case of how you are trying to use it, it will result in null in the Validate method

request.bar == null

because the It.IsAny expression is not actually passing anything into the method.

Read up on how to use Moq via the Moq Quickstart to get a better understanding of how to use it when testing.

Rewrite the test as

public void SomeDemoServiceTest() {
    //Arrange
    var mockProvider = new Mock<IDemoProvider>();
    mockProvider.Setup(p => p.GetStringById(It.IsAny<int?>())).Returns(() => "success");

    var mockService = new Mock<SomeDemoService>(mockProvider.Object) { CallBase = true };
    mockService.Setup(s => s.Validate(It.IsAny<ServiceRequest>())).Returns(true);

    var request = new ServiceRequest();

    //Act
    var result = mockService.Object.Post(request);

    //Assert
    Assert.AreEqual("success", result);
}

And the test should pass.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM