Aeterna
Aeterna

Reputation: 366

ASP.NET MVC5: Unit Testing a controller with a session

I have read many blogs and comments about Mocking a session object or using a fake objects, but i still cannot translate those answers to my own code.

Here is the Index Action of my UserController, that uses dependency injection to inject a IUserRepository into the constructor:

// GET: User
    public ActionResult Index()
    {
        User user = (User) Session["CurrentUser"];
        if (user != null) { 
            if(_repository.UserHasAdminAcces(user))
                return View(_repository.GetAllUsers().ToList());

            return RedirectToAction("DisplayErrorPage", "Error", new { errorMessage = "You have to be an Admin to enter this part" });
        }
        return RedirectToAction("Login");
    }

My Test Method currently looks like this:

public void TestIndexForValidUser()
    {           
        var mock = new Mock<IUserRepository>();
        mock.Setup(x => x.UserHasAdminAcces(It.IsAny<User>())).Returns(true);

        UserController target = new UserController(mock.Object);

        // create mock HttpContext
        var context = new Mock<ControllerContext>();

        target.ControllerContext = context.Object;

        var result = target.Index() as ViewResult;

        Assert.AreEqual(result, "Index");

    }

I want to give the ControllerContext a session object that returns a fake user and make sure that a View called Index is returned

Upvotes: 5

Views: 6145

Answers (3)

user326608
user326608

Reputation: 2548

Here's my solution for this in MVC5 using POCO mocking instead of a library like Moq.

            HttpContext.Current = MockHttpContext.FakeHttpContext(true, "[email protected]", true, "http://somesite.com/Path/To");
            var httpContextBase = new HttpContextWrapper(HttpContext.Current);
            InternalController controller = new InternalController();
            RouteData route_data = new RouteData();
            ControllerContext controller_context = new ControllerContext(httpContextBase, route_data, controller);
            controller.ControllerContext = controller_context;

The above sets up the MVC controller, and then I test a View call like so:

var result = controller.SomeMethod(somevar) as ViewResult;

And then assert on the result any model bits it contains.

My FakeHttpContext.cs class:

    public static HttpContext FakeHttpContext(bool login = true, string user = "[email protected]", bool add_role = true, string url = "http://tempuri.org/")
    {
        if(add_role)
        {
            MockRoleProvider mock = Roles.Provider as MockRoleProvider;
            if (mock == null) throw new NullReferenceException("MockRoleProvider null exception.");
            mock.ClearAll();
            mock.CreateUser(user);
            mock.CreateRole("User");
            mock.AddUsersToRoles(new string[] { user }, new string[] { "User" });
        }

        var httpRequest = new HttpRequest("", url, "");
        var stringWriter = new StringWriter();
        var httpResponse = new HttpResponse(stringWriter);
        var httpContext = new HttpContext(httpRequest, httpResponse);

        var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
                                                new HttpStaticObjectsCollection(), 10, true,
                                                HttpCookieMode.AutoDetect,
                                                SessionStateMode.InProc, false);

        httpContext.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
                                    BindingFlags.NonPublic | BindingFlags.Instance,
                                    null, CallingConventions.Standard,
                                    new[] { typeof(HttpSessionStateContainer) },
                                    null)
                            .Invoke(new object[] { sessionContainer });

        if(login)
        {
            // User is logged in
            httpContext.User = new GenericPrincipal(
                new GenericIdentity(user),
                new string[0]
                );
            httpContext.Session["UserID"] = user;
        }
        else
        {
            // User is logged out
            httpContext.User = new GenericPrincipal(
                new GenericIdentity(String.Empty),
                new string[0]
                );
        }

        return httpContext;
    }

For completeness, here's my MockRoleProvider.cs:

public class MockRoleProvider : RoleProvider
{
    public MockRoleProvider()
   : base()
    {
        Users = new List<User>();
        Roles = new List<Role>();
    }

    #region RoleProvider members
    public override void AddUsersToRoles(string[] usernames, string[] roleNames)
    {
        if (usernames == null) throw new ArgumentNullException("usernames");
        if (roleNames == null) throw new ArgumentNullException("roleNames");

        foreach (string role in roleNames)
        {
            if (!RoleExists(role)) throw new ProviderException("Role name does not exist.");
        }

        foreach (string user in usernames)
        {
            if (Users.FirstOrDefault(u => u.Username == user) == null) throw new ProviderException("Username does not exist.");
        }

        foreach (string username in usernames)
        {
            User user = Users.FirstOrDefault(u => u.Username == username);
            if (user == null) continue;
            foreach (var rolename in roleNames)
            {
                Role role = Roles.FirstOrDefault(r => r.RoleName == rolename);
                user.Roles.Add(role);
                role.Users.Add(user);
            }
        }
    }

    public override string ApplicationName { get; set; }

    public override void CreateRole(string roleName)
    {
        if (roleName == null || roleName == "") throw new ProviderException("Role name cannot be empty or null.");
        if (roleName.Contains(",")) throw new ArgumentException("Role names cannot contain commas.");
        if (RoleExists(roleName)) throw new ProviderException("Role name already exists.");

        Roles.Add(new Role { RoleName = roleName });
    }

    public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
    {
        if (roleName == null || roleName == "") throw new ProviderException("Role name cannot be empty or null.");

        Role role = Roles.FirstOrDefault(r => r.RoleName == roleName);

        if (role == null) throw new ProviderException("Role name does not exist.");
        if (throwOnPopulatedRole && GetUsersInRole(roleName).Length > 0) throw new ProviderException("Cannot delete a populated role.");

        Roles.Remove(Roles.FirstOrDefault(r => r.RoleName == roleName));
        return true;
    }

    public override string[] FindUsersInRole(string roleName, string usernameToMatch)
    {
        return GetUsersInRole(roleName).Where(n => n.Contains(usernameToMatch)).ToArray();
    }

    public override string[] GetAllRoles()
    {
        return Roles.Select(r => r.RoleName).ToArray();
    }

    public override string[] GetRolesForUser(string username)
    {
        if (username == null || username == "") throw new ProviderException("User name cannot be empty or null.");

        User user = Users.FirstOrDefault(u => u.Username == username);

        if (user == null) return new string[0];

        return user.Roles.Select(r => r.RoleName).ToArray();
    }

    public override string[] GetUsersInRole(string roleName)
    {
        if (roleName == null || roleName == "") throw new ProviderException("Role name cannot be empty or null.");

        Role role = Roles.FirstOrDefault(r => r.RoleName == roleName);

        if (role == null) throw new ProviderException("Role '" + roleName + "' does not exist.");

        return role.Users.Select(u => u.Username).OrderBy(n => n).ToArray();
    }

    public override bool IsUserInRole(string username, string roleName)
    {
        if (username == null || username == "") throw new ProviderException("User name cannot be empty or null.");
        if (roleName == null || roleName == "") throw new ProviderException("Role name cannot be empty or null.");

        Role role = Roles.FirstOrDefault(r => r.RoleName == roleName);

        return role != null && role.Users.FirstOrDefault(u => u.Username == username) != null;
    }

    public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
    {
        foreach (string roleName in roleNames)
        {
            if (roleName == null || roleName == "") throw new ProviderException("Role name cannot be empty or null.");
            if (!RoleExists(roleName)) throw new ProviderException("Role name not found.");
        }

        foreach (string username in usernames)
        {
            if (username == null || username == "") throw new ProviderException("User name cannot be empty or null.");

            foreach (string roleName in roleNames)
            {
                if (!IsUserInRole(username, roleName)) throw new ProviderException("User is not in role.");
            }
        }

        foreach (string username in usernames)
        {
            User user = Users.FirstOrDefault(u => u.Username == username);
            if (user == null) continue;
            foreach (string roleName in roleNames)
            {
                Role role = user.Roles.FirstOrDefault(r => r.RoleName == roleName);
                role.Users.Remove(user);
                user.Roles.Remove(role);
            }
        }
    }

    public override bool RoleExists(string roleName)
    {
        if (roleName == null || roleName == "") throw new ProviderException("Role name cannot be empty or null.");

        return Roles.FirstOrDefault(r => r.RoleName == roleName) != null;
    }
    #endregion

    public void ClearAll()
    {
        Users = new List<User>();
        Roles = new List<Role>();
    }

    public void ClearRoles()
    {
        Roles = new List<Role>();
        Users.ForEach(u => u.Roles = new List<Role>());
    }

    public void ClearUsers()
    {
        Users = new List<User>();
        Roles.ForEach(r => r.Users = new List<User>());
    }

    public void CreateUser(string username)
    {
        if (username == null || username == "") throw new ProviderException("User name cannot be empty or null.");
        if (UserExists(username)) throw new ProviderException("User name already exists.");

        Users.Add(new User { Username = username });
    }

    public bool DeleteUser(string username)
    {
        if (username == null || username == "") throw new ProviderException("User name cannot be empty or null.");

        User user = Users.FirstOrDefault(u => u.Username == username);

        if (user == null) throw new ProviderException("User name does not exist.");

        foreach (Role role in user.Roles)
        {
            role.Users.Remove(user);
        }
        Users.Remove(user);
        return true;
    }

    public bool UserExists(string username)
    {
        if (username == null || username == "") throw new ProviderException("User name cannot be empty or null.");

        return Users.FirstOrDefault(u => u.Username == username) != null;
    }

    private List<Role> Roles { get; set; }

    private List<User> Users { get; set; }

    private class Role
    {
        public Role()
        {
            Users = new List<User>();
        }

        public string RoleName { get; set; }
        public List<User> Users { get; set; }
    }

    private class User
    {
        public User()
        {
            Roles = new List<Role>();
        }

        public string Username { get; set; }
        public List<Role> Roles { get; set; }
    }
}

And lastly from the app.config in my unit test project:

  <system.web>
    <roleManager enabled="true" defaultProvider="MockRoleProvider">
      <providers>
        <add name="MockRoleProvider" type="MyProject.Tests.MockRoleProvider, MyProject.Tests" />
      </providers>
    </roleManager>
  </system.web> 

I also run a mock membership class and config, and use Effort to provide EF with a fake db. In terms of an integration test environment that runs just like any other unit test, it's been perfect for my purposes.

Upvotes: 1

Andrei
Andrei

Reputation: 44550

I use this approach:

var controller = new HomeController();
var context = MockRepository.GenerateStub<ControllerContext>();
context.Expect(x => x.HttpContext.Session["MyKey"]).Return("MyValue");
controller.ControllerContext = context;

See ScottGu's blog post.

Upvotes: 7

CountZero
CountZero

Reputation: 6379

Already answered here. How do you mock the session object collection using Moq

Another options is to have an ISession adapter which allows you to mock it by having your IOC container inject it into the service.

Something like this.

container.Register(Component.For<HttpSessionStateBase>()
      .LifeStyle.PerWebRequest
      .UsingFactoryMethod(() => new HttpSessionStateWrapper(HttpContext.Current.Session)));

Upvotes: 0

Related Questions