Asp.net-Mvc

ASP .Net MVC 3:單元測試控制器動作

  • October 2, 2017

我對單元測試和模擬概念很陌生。我試圖弄清楚如何為下面的基本開箱即用使用者註冊程式碼編寫一個好的測試案例:

[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);
}

以下是我需要您的意見/幫助的一些具體點:

  1. 我不一定要在 ASP .Net 成員數據庫中創建新使用者。
  2. 根據傳入的模型,我如何真正確定使用者是否成功註冊或過程中有錯誤。

你的程式碼有問題。您的操作取決於靜態方法:Membership.CreateUser. 如您所知,靜態方法是用於單元測試的 PITA。

因此,您可以通過引入抽象級別來削弱耦合:

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

然後有一些使用目前會員提供者的實現:

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;
   }
}

最後是控制器:

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);
   }
}

好的,既然我們已經削弱了耦合,我們可以使用一個模擬框架來模擬單元測試中的服務並使其變得微不足道。

例如,使用 Rhino Mocks,您可以創建以下測試來涵蓋 2 個失敗案例:

[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);
}

最後的測試是成功案例。我們仍然有一個問題。問題是以下行:

FormsAuthentication.SetAuthCookie(model.UserName, false);

這是什麼?這是一個靜態方法呼叫。因此,我們採用與成員資格提供者相同的方式來削弱控制器和表單身份驗證系統的耦合。

引用自:https://stackoverflow.com/questions/9799200