NullReferenceException when passing Arg.Any<int>() as argument using NSubstitute

I have a customer class which accepts an IDbGateway interface as a constructor parameter. I need to write a unit test CalculateWage_HourlyPayed_ReturnsCorrectWage for the class using NUnit and NSubstitute. My unit test works fine when I pass anyId. But I want to pass Arg.Any<int>() instead of anyId. At the moment my test fails because decimal actual = sut.CalculateWage(Arg.Any<int>()); gives NullReferenceException. Why is my code working for anyId value pass but not for Arg.Any<int>()?

Here are the target members

public class Customer
{
    private readonly IDbGateway _gateway;

    public Customer(IDbGateway gateway)
    {
        _gateway = gateway;
    }

    public decimal CalculateWage(int id)
    {
        WorkingStatistics ws = _gateway.GetWorkingStatistics(id);

        decimal wage;

        if (ws.PayHourly)
        {
            wage = ws.WorkingHours * ws.HourSalary;
        }
        else
        {
            wage = ws.MonthSalary;
        }

        return wage;
    }
}

public interface IDbGateway
{
    WorkingStatistics GetWorkingStatistics(int id);
}

public class DbGateway : IDbGateway
{
    public WorkingStatistics GetWorkingStatistics(int id)
    {
        throw new NotImplementedException();
    }
}
public class WorkingStatistics
{
    public decimal HourSalary { get; set; }
    public int WorkingHours { get; set; }
    public decimal MonthSalary { get; set; }
    public bool PayHourly { get; set; }
}

Here is the test in question

[TestFixture]
public class CustomerTestsWithNSubstitute
{
    [Test]
    public void CalculateWage_HourlyPayed_ReturnsCorrectWage()
    {
        var gateway = Substitute.For<IDbGateway>();
        var workingStatistics = new Business.Demo.WorkingStatistics()
        { PayHourly = true, HourSalary = 100, WorkingHours = 10 };
        const int anyId = 1;
        //gateway.GetWorkingStatistics(anyId).ReturnsForAnyArgs(workingStatistics);
        gateway.GetWorkingStatistics(Arg.Any<int>()).ReturnsForAnyArgs(workingStatistics);

        const decimal expectedWage = 100 * 10;
        var sut = new Customer(gateway);

        //decimal actual = sut.CalculateWage(anyId);
        decimal actual = sut.CalculateWage(Arg.Any<int>());

        Assert.That(actual, Is.EqualTo(expectedWage).Within(0.1));
    }
}

>Solution :

Arg.* are meant to be used in arranging/stubbing the mocked behavior.

They are not meant to be used as actual parameter values when exercising the test

Argument matchers should only be used when specifying calls for the purposes of setting return values, checking received calls, or configuring callbacks (for example: with Returns, Received or When). Using Arg.Is or Arg.Any in other situations can cause your tests to behave in unexpected ways.

Reference How NOT to use argument matchers

[TestFixture]
public class CustomerTestsWithNSubstitute {
    [Test]
    public void CalculateWage_HourlyPayed_ReturnsCorrectWage() {
        //Arrange
        var gateway = Substitute.For<IDbGateway>();
        var workingStatistics = new Business.Demo.WorkingStatistics()
        { PayHourly = true, HourSalary = 100, WorkingHours = 10 };
        const int anyId = 1;
        
        //Use Arg.* here
        gateway.GetWorkingStatistics(Arg.Any<int>()).ReturnsForAnyArgs(workingStatistics);

        const decimal expectedWage = 100 * 10;
        var sut = new Customer(gateway);

        //Act
        decimal actual = sut.CalculateWage(anyId); //<-- Use actual value here

        //Assert    
        Assert.That(actual, Is.EqualTo(expectedWage).Within(0.1));
    }
}

Reference Argument matchers

Leave a Reply