Search code examples
c#asp.net-mvcunit-testingmstestfakeiteasy

Object reference not set to an instance of an object at ASP.NET MVC Session on Unit Testing using Fake it Easy


I have an ASP.NET MVC 4 project with a controller that calls an external WCF to authenticate user login on the VerifyAccount method. This external WCF returns an AuthModelUserVerification class back to the controller and creates a Session containing a user id:

[HttpPost]
public ActionResult VerifyAccount(string username, string password) {

    AuthModelUserVerification result = lms_client.VerifyAccount(username, password);

    if (!result.isAuthenticated)
        return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);

    Session["SID"] = result.userid;

    return new HttpStatusCodeResult(HttpStatusCode.OK);
}

Below is the structure of the AuthModelUserVerification from the WCF:

public class AuthModel
{
    public class UserVerification {
        public int? userid { get; set; }
        public bool isAuthenticated { get; set; }

        public UserVerification()
        {
            userid = null;
            isAuthenticated = false;
        }
    }
}

I am trying to make a unit test on VerifyAccount method to test the status code being returned to the browser under certain conditions. I am using MSTest (.NET) and Fake it Easy mocking framework. The issue lies upon setting the value on the Session["SID"]

Session["SID"] = result.userid;

I am receiving the following error on this line when I debug the test:

Object reference not set to an instance of an object

While debugging the test, everytime I hover to the Session["SID"], it says null but the result.userid shows it has a value of 1 since I am passing a value to it via calling the mock service I made. Please see the implementation of my test here:

private readonly AuthController _controller_Auth;
private readonly ILMS_Service _lms_service;

public Auth_UnitTest() {
    _lms_service = A.Fake<ILMS_Service>();
    _controller_Auth = new AuthController(_lms_service);
}

[TestMethod]
public void VerifyAccount_Success()
{
    //Arrange
    string username = "admin";
    string password = "sampleP@sswoRd";
    int userID = 1;

    int expected_response_code = 200;
    var session = A.Fake<HttpSessionStateBase>();

    A.CallTo(() => session["SID"]).Returns(userID);

    A.CallTo(() => _lms_service.VerifyAccount(username, password))
        .Returns(new AuthModelUserVerification
        {
            userid = userID,
            isAuthenticated = true
        });

    //Act
    var result = _controller_Auth.VerifyAccount(username, password) as HttpStatusCodeResult;

    //Assert
    Assert.AreEqual(expected_response_code, result.StatusCode);
}

The mock is working since the isAuthenticated has the value of true when I debug it. It's the Session that isn't working. Even making a fake HttpSessionStateBase didn't resolve the issue. I am new to unit testing and I am still exploring things, any help would be apprecited. Thanks!

UPDATE

Based on @Blair Conrad's answer I ended up with this code on my test.

private readonly AuthController _controller_Auth;
private readonly ILMS_Service _lms_service;

private readonly HttpContextBase httpContext;
private readonly HttpResponseBase httpResponse;
private readonly HttpSessionStateBase httpSession;

public Auth_UnitTest() 
{
    // Mock WCF
    _lms_service = A.Fake<ILMS_Service>();

    // SUTs
    _controller_Auth = new AuthController(_lms_service);

    // Fake session
    httpContext = A.Fake<HttpContextBase>();
    httpResponse = A.Fake<HttpResponseBase>();
    httpSession = A.Fake<HttpSessionStateBase>();

    A.CallTo(() => httpContext.Response).Returns(httpResponse);
    A.CallTo(() => httpContext.Session).Returns(httpSession);
}

[TestMethod]
public void VerifyAccount_Authorized()
{
    //Arrange
    string username = "admin";
    string password = "sampleP@sswoRd";
    int? userID = 1;

    int expected_statusCode = (int)HttpStatusCode.OK;
    string expected_description = "Authorized";

    var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), _controller_Auth);
    _controller_Auth.ControllerContext = context;

    A.CallTo(() => _lms_service.VerifyAccount(username, password))
        .Returns(new AuthModelUserVerification
        {
            userid = userID,
            isAuthenticated = true,
            isActive = true
        });

    //Act
    var _authUser = _controller_Auth.VerifyAccount(username, password) as JsonResult;
    dynamic result = _authUser.Data;

    //Assert
    Assert.AreEqual(expected_statusCode, result.statusCode);
    Assert.AreEqual(expected_description, result.description);
}

From what I can understand, you need to fake the HttpContextBase, HttpResponseBase, and finally HttpSessionStateBase to fake the session being used on my controller. This works wonderfully as it have the expected values. I made the fakes of these classes as global and faked them on my test constructor class. If someone found a potential issue on my implementation, please let me know. Thanks!


Solution

  • I see session being created and configured, but no connection between it and _controller_Auth. I suspect the latter is using a Session property that is uninitialized. The faked session object needs to be provided to the system under test.

    I'm not an ASP user, but I think it's more complicated than how you injected your _lms_service. Faking Session in MVC and FakeItEasy (warning: link is up and down) has an example that I have not tried. I will lightly edit:

    var sut = new HomeController();
    
    var httpContext = A.Fake<HttpContextBase>();
    var httpResponse = A.Fake<HttpResponseBase>();
    var httpSession = A.Fake<HttpSessionStateBase>();
                
    A.CallTo(() => httpContext.Response).Returns(httpResponse);
    A.CallTo(() => httpContext.Session).Returns(httpSession);
    
    var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), sut);
    sut.ControllerContext = context;
    // ...