.NET Core unit test how to Mock HttpContext RemoteIpAddress?

Viewed 3584

I have a .NET Core 3.1 project using Identity. For the Login page handler I have added a line of code that after a user logs in, it updates a users location based on their IP address:

_locationRepository.UpdateUserLocationAsync(HttpContext.Connection.RemoteIpAddress);

Full Code

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");

    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            _locationRepo.UpdateUserLocation(HttpContext.Connection.RemoteIpAddress);
            _logger.LogInformation("User logged in.");
            return LocalRedirect(returnUrl);
        }
        if (result.RequiresTwoFactor)
        {
            return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
        }
        if (result.IsLockedOut)
        {
            _logger.LogWarning("User account locked out.");
            return RedirectToPage("./Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return Page();
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

My problem is when writing the unit test, I don't know how to properly mock the HttpContext. I keep getting a null reference exception regardless of what I have tried.

var httpContext = new Mock<HttpContext>();
httpContext.Setup(x => x.Connection.RemoteIpAddress).Returns(new IPAddress(16885952));

How do I mock the RemoteIpAddress?

3 Answers

Xunit + FakeItEasy

            var mockHttpContextAccessor = new Fake<IHttpContextAccessor>();
            var httpContext = new DefaultHttpContext()
            {
                Connection =
                {
                    RemoteIpAddress = new IPAddress(16885952)
                }
            };
            mockHttpContextAccessor.CallsTo(x => x.HttpContext)
                .Returns(httpContext);
            var extractedIpAddress = IpExtractor.GetIpAddress(httpContext);
            //assert 192.168.1.1

Here is my solution

public static void MockIpAddress(this ControllerBase controller) 
    {
        controller.ControllerContext = new ControllerContext()
        {
            HttpContext = new DefaultHttpContext()
            {
                Connection =
                {
                    RemoteIpAddress = new System.Net.IPAddress(16885952)
                }
            }
        };
    }

The setup you have won't match any call. You need to mock the Connection as well:

var connectionMock = new Mock<Microsoft.AspNetCore.Http.ConnectionInfo>(MockBehavior.Strict);
connectionMock.SetupGet(c => c.RemoteIpAddress).Returns(new IPAddress(16885952));

var httpContext = new Mock<HttpContext>(MockBehavior.Strict);
httpContext.SetupGet(x => x.Connection).Returns(connectionMock.Object);

Set your MockBehavior to Strict to throw exceptions when calls are made to properties or methods that weren't set up properly.

Related