2

I have the following method under test:

public HomeController(IUserIpAddressHelper userIpAddressHelper)
{
    _userIpAddressHelper = userIpAddressHelper;
}

[HttpGet]
public ActionResult Index()
{
    var userIpAddress = _userIpAddressHelper.GetUserIpAddress(System.Web.HttpContext.Current);
    if (_userIpAddressHelper.IsIpAddressOddOrEven(userIpAddress))
    {
        return RedirectToAction(HomePage);
    }
    
    return RedirectToAction(HomePageAlternative);
}

and I am testing as follows:

public void Test()
{
    var userIpAddressHelper = Substitute.For<IUserIpAddressHelper>();
    userIpAddressHelper.GetUserIpAddress(Arg.Any<HttpContext>()).Returns("0.0.0.2");
  
    var controller = new HomeController(userIpAddressHelper);

    var result = controller.Index();

    Assert.IsInstanceOf<RedirectToRouteResult>(result);

    var redirectToRouteResult = result as RedirectToRouteResult;
    Assert.AreEqual(HomeController.HomePage, redirectToRouteResult.RouteValues["action"]);
}

However the test is failing due to the value of userIpAddress being "" an empty string, instead of 0.0.0.2 as I've set it. Can anyone please point out where I've gone wrong here?

Jordan1993
  • 864
  • 1
  • 10
  • 28
  • I doubt `System.Web.HttpContext.Current` is set to anything meaningful in your unit-test. You should mock that context and pass the mocked object to your method. E.g. https://stackoverflow.com/questions/4379450/mock-httpcontext-current-in-test-init-method – MakePeaceGreatAgain Aug 20 '20 at 13:25

2 Answers2

1

Is userIpAddress definitely ""? It looks like the Returns in your original test is specified well, but if IUserIpAddressHelper is an interface then the substitute for it will not have a result stubbed for IsIpAddressOddOrEven, so it will always return false even if GetUserIpAddress is stubbed to return "0.0.0.2".

To get the test to mirror how the production code passes through the data, you can stub out both members:

var userIpAddressHelper = Substitute.For<IUserIpAddressHelper>();
userIpAddressHelper.GetUserIpAddress(Arg.Any<HttpContext>()).Returns("0.0.0.2");
userIpAddressHelper.IsIpAddressOddOrEven("0.0.0.2").Returns(true);

This will test that the production code correctly passes through the result of GetUserIpAddress to IsIpAddressOddOrEven.

Note: we could also stub these to work with "ip-address-result" and it would still work. We don't need a valid odd/even result returned, as we are not using a real implementation of IUserIpAddressHelper, just a substitute for testing. If you find it necessary to substitute for IUserIpAddressHelper in lots of tests and you want it to act like a real implementation (i.e. it will actually return whether an address is odd or even), it might be easier to write a TestUserIpAddressHelper.

Another way to avoid having the dependency between the results of GetUserIpAddress and IsIpAddressOddOrEven is to change the interface to have a bool IsIpAddressOddOrEven(HttpContext context) method that combines both operations. That way you would only need to stub one for the test.

Dharman
  • 30,962
  • 25
  • 85
  • 135
David Tchepak
  • 9,826
  • 2
  • 56
  • 68
0

If you have problems with System.Web.HttpContext.Current, you can try to mock IsIpAddressOddOrEven method instead. They will both do the same job for your test. Like this:

public void Test()
{
    var userIpAddressHelper = Substitute.For<IUserIpAddressHelper>();
    userIpAddressHelper.IsIpAddressOddOrEven(Arg.Any<string>()).Returns(true);
  
    var controller = new HomeController(userIpAddressHelper);

    var result = controller.Index();

    Assert.IsInstanceOf<RedirectToRouteResult>(result);

    var redirectToRouteResult = result as RedirectToRouteResult;
    Assert.AreEqual(HomeController.HomePage, redirectToRouteResult.RouteValues["action"]);
}
Koray Elbek
  • 794
  • 5
  • 13