bigmac
bigmac

Reputation: 2553

Need Help Writing Unit Test that on Method that requires HttpContext.Current.User

I am trying to write some unit tests for my MVC3 project (the first tests for this project) and I am getting stumped. Basically, I have a MemberQueries class that is used by my MemberController to handle all the logic.

I want to start writing tests on this class and want to start with a simple example. I have a method in this class called IsEditModeAvailable which determines if the user is a member of the "Site Administrator" role or that the user is able to edit their own data, but no one elses. I determine the last requirement by comparing the passed in Id value to the HttpContext User property.

The problem that I'm running into is I don't know how to mock or inject the proper parameters into my unit tests when creating the MemberQueries object. I am using, NUnit, Moq and Ninject, but I'm just not sure how to write the code. If I'm just not structuring this properly, please let me know as I'm a complete noob to unit testing.

Here's a sample of the code from my MemberQueries class:

public class MemberQueries : IMemberQueries
{
  private readonly IRepository<Member> _memberRepository;
  private readonly IMemberServices _memberServices;
  private readonly IPrincipal _currentUser;

  public MemberQueries(IUnitOfWork unitOfWork, IMemberServices memberServices, IPrincipal currentUser)
  {
    _memberRepository = unitOfWork.RepositoryFor<Member>();
    _memberServices = memberServices;
    _currentUser = currentUser;
  }

  public bool IsEditModeAvailable(int memberIdToEdit)
  {
    if (_currentUser.IsInRole("Site Administrator")) return true;
    if (MemberIsLoggedInUser(memberIdToEdit)) return true;
    return false;
  }

  public bool MemberIsLoggedInUser(int memberIdToEdit)
  {
    var loggedInUser = _memberServices.FindByEmail(_currentUser.Identity.Name);
    if (loggedInUser != null && loggedInUser.Id == memberIdToEdit) return true;
    return false;
  }

}

Here's a sample from my MemberServices class (which is in my domain project, referenced by MemberQueries):

public class MemberServices : IMemberServices
{
  private readonly IRepository<Member> _memberRepository;

  public MemberServices(IUnitOfWork unitOfWork)
  {
    _memberRepository = unitOfWork.RepositoryFor<Member>();
  }

  public Member Find(int id)
  {
    return _memberRepository.FindById(id);
  }

  public Member FindByEmail(string email)
  {
    return _memberRepository.Find(m => m.Email == email).SingleOrDefault();
  }
}

Finally, here's the stub of the unit test I am trying to write:

[Test]
public void LoggedInUserCanEditTheirOwnInformation()
{
  var unitOfWork = new UnitOfWork();

  var currentUser = new Mock<IPrincipal>();
  // I need to somehow tell Moq that the logged in user has a HttpContext.User.Name of "[email protected]"

  var memberServices = new Mock<MemberServices>();
  // I then need to tell Moq that it's FindByEmail("[email protected]") method should return a member with a UserId of 1

  var memberQueries = new MemberQueries(unitOfWork, memberServices.Object, currentUser.Object);

  // If the logged in user is "[email protected]" who has an Id of 1, then IsEditModeAvailable(1) should return true
  Assert.IsTrue(memberQueries.IsEditModeAvailable(1));
}

Upvotes: 3

Views: 525

Answers (2)

Darin Dimitrov
Darin Dimitrov

Reputation: 1038730

It looks like you are trying to test the MemberQueries.IsEditModeAvailable method. You have 2 cases to cover here. The Site Administrators case and the case where there's a currently logged user whose id matches the one passed as argument. And since the MemberQueries class relies purely on interfaces you could mock everything:

[TestMethod]
public void EditMode_Must_Be_Available_For_Site_Administrators()
{
    // arrange
    var unitOfWork = new Mock<IUnitOfWork>();
    var currentUser = new Mock<IPrincipal>();
    currentUser.Setup(x => x.IsInRole("Site Administrator")).Returns(true);
    var memberServices = new Mock<IMemberServices>();
    var memberQueries = new MemberQueries(unitOfWork.Object, memberServices.Object, currentUser.Object);

    // act
    var actual = memberQueries.IsEditModeAvailable(1);

    // assert
    Assert.IsTrue(actual);
}

[TestMethod]
public void EditMode_Must_Be_Available_For_Logged_In_Users_If_His_Id_Matches()
{
    // arrange
    var unitOfWork = new Mock<IUnitOfWork>();
    var currentUser = new Mock<IPrincipal>();
    var identity = new Mock<IIdentity>();
    identity.Setup(x => x.Name).Returns("[email protected]");
    currentUser.Setup(x => x.Identity).Returns(identity.Object);
    currentUser.Setup(x => x.IsInRole("Site Administrator")).Returns(false);
    var memberServices = new Mock<IMemberServices>();
    var member = new Member
    {
        Id = 1
    };
    memberServices.Setup(x => x.FindByEmail("[email protected]")).Returns(member);
    var memberQueries = new MemberQueries(unitOfWork.Object, memberServices.Object, currentUser.Object);

    // act
    var actual = memberQueries.IsEditModeAvailable(1);

    // assert
    Assert.IsTrue(actual);
}

Actually there's a third case you need to cover: you have a currently logged in user, who is not a Site Administrator and whose id doesn't match the one passed as argument:

[TestMethod]
public void EditMode_Should_Not_Be_Available_For_Logged_In_Users_If_His_Id_Doesnt_Match()
{
    // arrange
    var unitOfWork = new Mock<IUnitOfWork>();
    var currentUser = new Mock<IPrincipal>();
    var identity = new Mock<IIdentity>();
    identity.Setup(x => x.Name).Returns("[email protected]");
    currentUser.Setup(x => x.Identity).Returns(identity.Object);
    currentUser.Setup(x => x.IsInRole("Site Administrator")).Returns(false);
    var memberServices = new Mock<IMemberServices>();
    var member = new Member
    {
        Id = 2
    };
    memberServices.Setup(x => x.FindByEmail("[email protected]")).Returns(member);
    var memberQueries = new MemberQueries(unitOfWork.Object, memberServices.Object, currentUser.Object);

    // act
    var actual = memberQueries.IsEditModeAvailable(1);

    // assert
    Assert.IsFalse(actual);
}

Upvotes: 3

Trevor Pilley
Trevor Pilley

Reputation: 16393

The good news is that you are passing the user as an IPrincipal into the code that needs it rather than referring to HttpContext.Current.User. All you should need to do is setup the mock IPrincipal so that it returns the vales you need for that test.

var mockIdentity = new Mock<IIdentity>();
mockIdentity.Setup(x => x.Name).Returns("[email protected]");

var mockPrincipal = new Mock<IPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(mockIdentity.Object);

Upvotes: 2

Related Questions