2013-11-09 39 views
3

我正在嘗試使用新的VS2013默認MVC w /身份驗證項目瞭解單元測試。我想要測試的第一件事就是註冊一個用戶。 (我知道我可能不需要單元測試,因爲它已經是MS測試的代碼,但我想用這個來理解基礎知識)。我也聽說新的會員代碼更「可測試」,所以我不需要創建我自己的會員界面,等等......單元測試MVC身份驗證VS 2013

我使用NSubstitute作爲僞造框架。

看着賬戶控制器 - >註冊()異步方法

namespace WebApplication1.Controllers 
{ 
    [Authorize] 
    public class AccountController : Controller 
    { 
     public AccountController() 
     : this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()))) 
     { 
     } 

     public AccountController(UserManager<ApplicationUser> userManager) 
     { 
      UserManager = userManager; 
     } 

     public UserManager<ApplicationUser> UserManager { get; private set; } 

     ... 


     // 
     // POST: /Account/Register 
     [HttpPost] 
     [AllowAnonymous] 
     [ValidateAntiForgeryToken] 
     public async Task<ActionResult> Register(RegisterViewModel model) 
     { 
      if (ModelState.IsValid) 
      { 
       var user = new ApplicationUser() { UserName = model.UserName }; 
       var result = await UserManager.CreateAsync(user, model.Password); 
       if (result.Succeeded) 
       { 
        await SignInAsync(user, isPersistent: false); 
        return RedirectToAction("Index", "Home"); 
       } 
       else 
       { 
        AddErrors(result); 
       } 
      } 

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

如果我想寫一個簡單的測試(如Register_RegisterValidUser),我會怎麼做呢?我知道我需要以某種方式取代了的UserManager但是這並沒有爲我工作:

var substitute = Substitute.For<UserManager<ApplicationUser>>(); 

我也明白,我需要繞過使用Task.FromResult異步任務<>的功能,但我不知道怎麼樣從CreateAsync()和SigninAsync()方法返回有效的對象。

有人可以幫助一些示例測試代碼?非常感謝!

回答

1

嘲笑與NSubstitute用戶管理器,你就必須使用這個:

var userStore = Substitute.For<IUserStore<ApplicationUser>>(); 
var userManager = Substitute.For<UserManager<ApplicationUser>>(userStore); 

現在你可以假的方法產生了。例如:

userManager.FindByNameAsync(Arg.Any<string>()).Returns(Task.FromResult(new ApplicationUser())); 

希望這對你有所幫助。

+0

看到這個,並嘗試它,它的工作原理!謝謝 –

1

測試指導

您可以單元測試夫婦的行爲,但我只是告訴你一個單一的單元測試只是驗證是否接收到正確的RedirectToAction值的方向。例如,

return RedirectToAction(「Index」,「Home」);

提高可測試性

在你的問題你提到

我還聽說,新成員的代碼更「可測」所以我 不需要創建我自己的會員界面等。

雖然這是真的,我會做一個小調整,所以我們可以n使你的實現更具可測性。這樣你的單元測試可以純粹專注於你正在進行的測試的具體行爲。換句話說,我們可以讓你SUT(被測系統)更具可測性。

await SignInAsync(user, isPersistent: false); 

我相信SignInAsync是一個私有方法。在這個方法中應該有一些行爲,你可能會提取出一個獨立的實現,你可以注入到SUT中。我們可以把這種ISignInManager

public interface ISignInManager { 
    Task SignInAsync(ApplicationUser user, bool isPersistent); 
} 

這樣做的好處是,現在你可以注入ISignInManager的behaiovr執行簽到任務,您的SUT變得更加可測試。你應該看到在你的單元測試中會有更少的嘲弄/殘留,並使你的測試更易於編寫和理解。

單元測試

你可以把新的異步的優勢/等待MSTest的方法的使用。這簡化了我們用來編寫的複雜和不可靠的測試。

驗證正確的重定向路由控制器/操作方法的單元測試可寫爲如下。

[TestMethod] 
    public async Task Register_RegisterValidUser_EnsureRedirectToIndexActionHomeController() 
    { 
     // Arrange 
     var userManagerStub = Substitute.For<IUserManager<ApplicationUser>>(); 
     var tcs = new TaskCompletionSource<IdentityResult>(); 
     tcs.SetResult(new IdentityResult(true)); 
     userManagerStub.CreateAsync(Arg.Any<ApplicationUser>(), Arg.Any<string>()).Returns(tcs.Task); 

     var signInManagerStub = Substitute.For<ISignInManager>>(); 

     signInManagerStub.Setup(s => s.SignInAsync(It.IsAny<ApplicationUser>(), It.IsAny<bool>())).Returns(Task.FromResult(true)); 

     var sut = new AccountController(userManagerStub) { SignInManager = signInManagerStub.Object }; 

     // Act 
     var result = await sut.Register(new RegisterViewModel() { Password = "fakePw" }) as RedirectToRouteResult; 

     // Assert 
     Assert.AreEqual<string>("Index", result.RouteValues["action"].ToString()); 
     Assert.AreEqual<string>("Home", result.RouteValues["controller"].ToString()); 
    } 

上面使用NSubstitute作爲隔離框架,但是如果有人對Moq版本感興趣,請看下面。

[TestMethod] 
    public async Task Register_RegisterValidUser_EnsureRedirectToIndexHome() 
    { 
     // Arrange 
     var userManagerStub = new Mock<IUserManager<ApplicationUser>>(); 
     var tcs = new TaskCompletionSource<IdentityResult>(); 
     tcs.SetResult(new IdentityResult(true)); 
     userManagerStub.Setup(s => s.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>())).Returns(tcs.Task); 

     var signInManagerStub = new Mock<ISignInManager>(); 
     signInManagerStub.Setup(s => s.SignInAsync(It.IsAny<ApplicationUser>(), It.IsAny<bool>())).Returns(Task.FromResult(true)); 

     var sut = new AccountController(userManagerStub.Object) {SignInManager = signInManagerStub.Object}; 

     // Act 
     var result = await sut.Register(new RegisterViewModel() { Password = "fakePw" }) as RedirectToRouteResult; 

     // Assert 
     Assert.AreEqual<string>("Index", result.RouteValues["action"].ToString()); 
     Assert.AreEqual<string>("Home", result.RouteValues["controller"].ToString()); 
    } 
+0

拉吉 - 感謝您的意見。我似乎無法得到TestMethod的工作(NSubstitute版本)。一個小點 - 似乎在那裏有一些Moq代碼(Mock )。但我也無法編譯它。例如,第一行var userManagerStub = Substitute.For >();說我需要IUserManager的程序集引用,但我不確定那是什麼。最後,你可以給我一些關於我在哪裏實現ISignInManager接口的更多細節。我需要綁定使用Ninject或其他東西。很抱歉,如果這些是明顯的問題 –

+0

@MikeSmith抱歉,遲到的迴應。是的,我錯誤地將moq代碼與NS混合在一起。它現在已經在更新的答案中修復了。 IUserManager是UserManager實現的接口。我剛剛創建,不知道你的UserManager是可嘲弄的。如果是,而不是IUserManager,只需使用UserManager。有了ISignInManager,我猜你有SignInAsync私有方法?這個接口抽象出實現,你可以通過屬性注入來注入它。 – Spock

+0

Spock - 再次感謝你,但它仍然沒有編譯。你的陳述(「我剛剛創建,不知道你的UserManager是否可以嘲笑」)是我試圖測試的。我使用VS 2013自帶的默認Membership。正如我在我的問題中提到的 - 這是行不通的(var substitute = Substitute.For >();) –