I am attempting to unit test my controllers, and I am using the default MVC 3 AccountController
unit tests as a basis. So far I have my controller, which looks like:
public partial class HomeController : MyBaseController
{
public HomeController(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; }
public virtual ActionResult Index()
{
return View();
}
public virtual ActionResult About()
{
return View();
}
}
MyBaseController
has the following code:
public class MyBaseController : Controller
{
public MyJobLeadsBaseController()
{
CurrentUserId = 0;
}
protected override void Initialize(RequestContext requestContext)
{
if (MembershipService == null) { MembershipService = new AccountMembershipService(); }
base.Initialize(requestContext);
// If the user is logged in, retrieve their login id
var user = MembershipService.GetUser();
if (user != null)
CurrentUserId = (int)user.ProviderUserKey;
}
public int CurrentUserId { get; set; }
public IMembershipService MembershipService { get; set; }
protected IUnitOfWork _unitOfWork;
}
Everything works correctly when I run the actual site, and breakpoints show that the Initialize()
is correctly being triggered. However, the following unit test never runs the Initialize(RequestContext)
method:
[TestMethod]
public void Index_Shows_When_Not_Logged_In()
{
// Setup
HomeController controller = new HomeController(_unitOfWork);
controller.MembershipService = new MockMembershipService(null);
SetupController(controller);
// Act
ActionResult result = controller.Index();
// Verify
Assert.IsNotNull(result, "Index returned a null action result");
Assert.IsInstanceOfType(result, typeof(ViewResult), "Index did not return a view result");
}
protected static void SetupController(Controller controller)
{
RequestContext requestContext = new RequestContext(new MockHttpContext(), new RouteData());
controller.Url = new UrlHelper(requestContext);
controller.ControllerContext = new ControllerContext
{
Controller = controller,
RequestContext = requestContext
};
}
Debugging through this unit test shows that at no point does the overridden MyBaseController.Initialize()
get called at all. This causes issues where my CurrentUserId
property is not being set in unit tests, but is being set on the live system.
What else do I have to do to trigger the Initialize()
to be called?
When you make a request to a controller through a website the MVC framework picks up that request and runs it through a number of different steps. Somewhere in that pipeline of steps MVC knows that it must call the Initialize
method so it finds the Initialize
in your MyBaseController
class and executes it. At this point all is well and everything works.
When you create a new instance of your HomeController
in your test code you're simply creating a new instance of a class. You're not running that class through the MVC request pipeline and your test framework doesn’t know to execute the Initialize
method so you get an error.
Personally I would look to test the Index
action independently of the Initialize
method. Your Index
action above is empty so I can't see exactly what you're trying to do but if you are returning one view for logged in users and another for anonymous users I'd do something like this:
[TestMethod]
public void Index_Shows_When_Not_Logged_In(){
HomeController controller = new HomeController(_unitOfWork);
controller.CurrentUserId=0;
controller.Index();
//Check your view rendered in here
}
[TestMethod]
public void Some_Other_View_Shows_When_Logged_In(){
HomeController controller = new HomeController(_unitOfWork);
controller.CurrentUserId=12; //Could be any value
controller.Index();
//Check your view rendered in here
}
There are some pretty nifty test helpers in the MVC contrib project (http://mvccontrib.codeplex.com/) which allow you to do things like:
controller.Index().AssertViewRendered();
The constructor does not call Initialize. IIRC What calls initialize is the Execute/ExecuteCore method.
Here is the code from the MVC source:
protected virtual void Execute(RequestContext requestContext) {
if (requestContext == null) {
throw new ArgumentNullException("requestContext");
}
if (requestContext.HttpContext == null) {
throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
}
VerifyExecuteCalledOnce();
Initialize(requestContext);
using (ScopeStorage.CreateTransientScope()) {
ExecuteCore();
}
}
This is basically called from the MvcHandler in the BeginProcessRequest method.
Update:
RequestContext does not exist in a unit test, because you're not going through ASP.NET. If I remember correctly you'd need to mock it.
Another problem in using Initialize in a test is the membership provider, which I haven't used myself, but I would guess that AccountMembershipService would fail you in a test? It seems to me that this would create fragile tests. It would probably also slow you down if it has to contact a server, and might fail you on a CI server.
IMO,from a basic look around the Init method, it shouldn't be there. The easiest solution,without breaking anything, that comes to mind is using dependency injection to inject the CurrentUserId in the Controller ctor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With