2012-03-21 91 views
8

我對單元測試和模擬概念相當陌生。我試圖找出如何寫一個好的測試用例的基本外的將下面框用戶註冊代碼:ASP .Net MVC 3:單元測試控制器動作

[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. 基於型號傳入,如何確保用戶是否已成功註冊或者存在過程中的錯誤。

回答

25

您的代碼有問題。你的行爲取決於一個靜態方法: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); 
    } 
} 

好吧,現在我們已經削弱了我們可以聯繫e嘲笑框架來嘲笑單元測試中的服務並使其變得微不足道。

例如使用犀牛製品,你可以創建下面的測試覆蓋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); 

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

+4

+1爲偉大和容易遵循步行。 – ljubomir 2012-04-26 08:52:28

0

爲了測試這種方法,你可以按照雙向

  1. 測試類中創建一個從Membership類繼承一個新類,覆蓋的方法CREATEUSER。
  2. 使用Moq來模擬課程。

對於第一種情況,我將檢查用戶名是否等於「GoodUser」或「BadUser」,並生成MembershipCreateStatus.Success或不同的狀態。

對於第二個我將設置兩個方法,遵循與其他方法相同的想法。舉例來看link