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));
}
}
CodePudding user response:
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
orWhen
). UsingArg.Is
orArg.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