This was pretty straightforward but I didn't find it written up anywhere on
the web so here goes. What follows is how I addressed testing IPrincipal in
ASP.NET MVC using xunit.net
Let's start with the test:
1 [Fact]
2 public void changes_password() {
3 //arrange
4 var controller = ObjectFactory.GetInstance<AccountController>();
5 controller.ControllerContext = new TestControllerContext();
6
7 //act
8 var result = controller.ChangePassword("password", "p455w0rd", "p455w0rd");
9
10 //assert
11 var viewResult = Assert.IsType<RedirectToRouteResult>(result);
12 }
notice line 5. why are we setting a custom ControllerContext here?
ControllerContext tells the current controller about things like HttpContext,
which will be unavailable during tests (whoops!) This is needed because
somewhere in the Action code, I am making a call into User.Identity.Name, which
is supplied by HttpContext at runtime. Again, since this isn't available during
testing, I have to tell the controller how to find the fake httpcontext used
then.
For this project, I'm using simple hand-rolled stubs. Here is the code for
TestControllerContext:
1 public class TestControllerContext : ControllerContext {
2 public override System.Web.HttpContextBase HttpContext {
3 get {
4 return new TestHttpContext();
5 }
6 set {
7 base.HttpContext = value;
8 }
9 }
10 }
instead of returning the runtime HttpContext (which doesn't exist, remember)
I'm supplying another hand-rolled stub. Here is that code:
1 public class TestHttpContext : HttpContextBase {
2 public override System.Security.Principal.IPrincipal User {
3 get {
4 return new TestPrincipal();
5 }
6 set {
7 base.User = value;
8 }
9 }
10 }
again, same principal :) because I'm concerned about calling into User for
the Identity.Name property, I return another stub that implements IPrincipal.
Here is that code:
1 public class TestPrincipal : IPrincipal{
2 public IIdentity Identity {
3 get { return new GenericIdentity("you@me.com"); }
4 }
5
6 public bool IsInRole(string role) {
7 throw new NotImplementedException();
8 }
9 }
now, when the controller code that was calling User.Identity.Name is tested,
it will return the name you@me.com. Line 11 of
the test code above asserts the test passes because if an error occurred, I
would redisplay the form with errors shown using the View() method. A redirect
says that the change password op worked:
1 if (MembershipService.ChangePassword(User.Identity.Name, currentPassword, newPassword))
2 return RedirectToAction("ChangePasswordSuccess");
hth.