Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP .Net MVC 3: Unit testing controller actions

I am quite new to Unit testing and Mock concepts. I am trying to figure out how to write a good test case for the basic out-of-the box user registration code below:

[HttpPost]
public ActionResult Register(RegisterModel model)
{
    if (ModelState.IsValid)
    {
        // Attempt to register the user
        MembershipCreateStatus createStatus;
        Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, out createStatus);

        if (createStatus == MembershipCreateStatus.Success)
        {
            FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */);
            return RedirectToAction("Index", "Home");
        }
        else
        {
            ModelState.AddModelError("", ErrorCodeToString(createStatus));
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Below are some of the specific points where I need your opinion/help:

  1. I do not necessarily want to create a new user in ASP .Net membership database.
  2. Based on the model passed in, how do I really make sure that if the user was successfully registered or there were errors in the process.
like image 234
Moon Avatar asked Mar 21 '12 05:03

Moon


1 Answers

You have a problem with your code. Your action depends on a static method: Membership.CreateUser. And as you know static methods are PITAs to unit test.

So you could weaken the coupling by introducing a level of abstraction:

public interface IMyService
{
    MembershipCreateStatus CreateUser(string username, string password, string email);
}

and then have some implementation that would use the current Membership provider:

public class MyService: IMyService
{
    public MembershipCreateStatus CreateUser(string username, string password, string email)
    {
        MembershipCreateStatus status;
            Membership.CreateUser(username, password, email, null, null, true, null, out status);
        return status;
    }
}

and finally the controller:

public class AccountController : Controller
{
    private readonly IMyService _service;
    public AccountController(IMyService service)
    {
        _service = service;
    }

    [HttpPost]
    public ActionResult Register(RegisterModel model)
    {
        if (ModelState.IsValid)
        {
            // Attempt to register the user
            var status = _service.CreateUser(model.UserName, model.Password, model.Email);
            if (status == MembershipCreateStatus.Success)
            {
                FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */);
                return RedirectToAction("Index", "Home");
            }
            else
            {
                ModelState.AddModelError("", ErrorCodeToString(createStatus));
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }
}

OK, now that we have weakened the coupling we could use a mocking framework to mock the service in the unit test and make it trivial.

For example using Rhino Mocks you could create the following tests to cover the 2 failure cases:

[TestMethod]
public void Register_Action_Should_Redisplay_View_If_Model_Is_Invalid()    
{
    // arrange
    var sut = new AccountController(null);
    var model = new RegisterModel();
    sut.ModelState.AddModelError("", "invalid email");

    // act
    var actual = sut.Register(model);

    // assert
    Assert.IsInstanceOfType(actual, typeof(ViewResult));
    var viewResult = actual as ViewResult;
    Assert.AreEqual(model, viewResult.Model);
}

[TestMethod]
public void Register_Action_Should_Redisplay_View_And_Add_Model_Error_If_Creation_Fails()
{
    // arrange
    var service = MockRepository.GenerateStub<IMyService>();
    service
        .Stub(x => x.CreateUser(null, null, null))
        .IgnoreArguments()
        .Return(MembershipCreateStatus.InvalidEmail);
    var sut = new AccountController(service);
    var model = new RegisterModel();

    // act
    var actual = sut.Register(model);

    // assert
    Assert.IsInstanceOfType(actual, typeof(ViewResult));
    var viewResult = actual as ViewResult;
    Assert.AreEqual(model, viewResult.Model);
    Assert.IsFalse(sut.ModelState.IsValid);
}

The final test is the success case. We still have an issue with it. The issue is the following line:

FormsAuthentication.SetAuthCookie(model.UserName, false);

What is this? It is a static method call. So we proceed the same way as we did with the membership provider to weaken the coupling of our controller and the forms authentication system.

like image 169
Darin Dimitrov Avatar answered Nov 01 '22 20:11

Darin Dimitrov