我不能在當前的例子中詳細說明單元測試。因爲你所得到的只有一個方法ShowDetails
,它會返回View
與UsersIdentificationViewModel
並列。由於您僅返回View
,因此您可以將退貨類型更改爲ViewResult
。然後,您可以測試的是與View
綁定的型號是否爲UsersIdentificationViewModel
。
如果我們舉一個簡單的例子,我可以更好地解釋MVC中的單元測試。如果您創建一個新的MVC應用程序選擇internet template
你會看到一個AccountController
被定義爲默認值,它包含登錄,寄存器,修改密碼的操作等
讓我們以LogOn
行動在AccountController
。
AccountController.cs
public class AccountController : Controller
{
private readonly IAuthProvider _authProvider;
public AccountController(IAuthProvider authProvider)
{
_authProvider = authProvider;
}
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (_authProvider.ValidateUser(model.UserName, model.Password))
{
_authProvider.SetCookie(model.UserName, model.RememberMe);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
return View(model);
}
...
}
爲了避免與FormsAuthentication
與AccountController
密封類的依賴我已經使用的接口IAuthProvider
簡化單元測試。
IAuthProvider.cs
public interface IAuthProvider
{
bool ValidateUser(string username, string password);
void SetCookie(string username, bool rememberMe);
bool CreateUser(string username, string password, string email, out string error);
bool ChangePassword(string username, string oldPassword, string newPassword);
void SignOut();
}
LogOnModel.cs
public class LogOnModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
您可以注意到在LogOn
動作很多的if..else條件,他們是單位好candiates測試。
我會爲這個動作寫至少四個單元測試。
- 驗證輸入有效憑證的動作應該重定向到傳遞的URL。
- 驗證通過有效憑證沒有
returnUrl
AccountController
應該將用戶重定向到Home
操作。
- 驗證傳遞無效憑證時,帳戶控制器應返回錯誤視圖。
- 驗證何時出現驗證錯誤,控制器應返回錯誤視圖。
下面是我用MSTest
和RhinoMocks
編寫的單元測試。
AccountControllerTests.cs
[TestClass]
public class AccountControllerTests
{
private AccountController _accountController;
private IAuthProvider _mockAuthProvider;
[TestInitialize]
public void SetUp()
{
//** Arrange
_mockAuthProvider = MockRepository.GenerateStub<IAuthProvider>();
_accountController = new AccountController(_mockAuthProvider);
}
[TestCleanup]
public void CleanUp()
{
}
/// <summary>
/// This test is to verify on entering valid credentials the action should redirect to the passed url.
/// </summary>
[TestMethod]
public void LogOn_Action_Valid_Credentials_With_ReturnUrl_Test()
{
//** Arrange
var logonModel = new LogOnModel
{
UserName = "trigent",
Password = "password",
RememberMe = true
};
// stub the ValidateUser to return "true" to pretend the user is valid.
_mockAuthProvider.Stub(x => x.ValidateUser(logonModel.UserName, logonModel.Password)).Return(true);
//** Act
var actual = _accountController.LogOn(logonModel, "/");
//** Assert
// verify RedirectResult is returned from action
Assert.IsInstanceOfType(actual, typeof(RedirectResult));
// verify the redirect url is same as the passed one.
Assert.AreEqual(((RedirectResult)actual).Url, "/");
}
/// <summary>
/// This test is to verify on passing valid credentials without returnUrl the account controller
/// should redirect the user to the "Home" action.
/// </summary>
[TestMethod]
public void LogOn_Action_Valid_Credentials_Without_ReturnUrl_Test()
{
//** Arrange
var logonModel = new LogOnModel
{
UserName = "trigent",
Password = "password",
RememberMe = true
};
// stub the ValidateUser to return "true" to pretend the user is valid.
_mockAuthProvider.Stub(x => x.ValidateUser(logonModel.UserName, logonModel.Password)).Return(true);
//** Act
var actual = _accountController.LogOn(logonModel, string.Empty);
//** Assert
// verify RedirectToRouteResult is returned from action
Assert.IsInstanceOfType(actual, typeof(RedirectToRouteResult));
// verify the controller redirecting to "Home" action.
var routeValues = ((RedirectToRouteResult)actual).RouteValues;
Assert.AreEqual("Home", routeValues["controller"].ToString());
Assert.AreEqual("Index", routeValues["action"].ToString());
}
/// <summary>
/// This test is to verify on passing invalid credentials the account controller should return the login view
/// with error messages.
/// </summary>
[TestMethod]
public void LogOn_Action_Invalid_Credentials_Test()
{
//** Arrange
var logonModel = new LogOnModel
{
UserName = "trigent",
Password = "password",
RememberMe = true
};
// stub the ValidateUser to return "false" to pretend the user is invalid.
_mockAuthProvider.Stub(x => x.ValidateUser(logonModel.UserName, logonModel.Password)).Return(false);
//** Act
var actual = _accountController.LogOn(logonModel, string.Empty);
//** Assert
// verify ViewResult is returned from action
Assert.IsInstanceOfType(actual, typeof(ViewResult));
// verify the controller throws error.
var modelStateErrors = _accountController.ModelState[""].Errors;
Assert.IsTrue(modelStateErrors.Count > 0);
Assert.AreEqual("The user name or password provided is incorrect.", modelStateErrors[0].ErrorMessage);
}
/// <summary>
/// This test is to verify when there is a validation error the controller should return the same login view.
/// </summary>
[TestMethod]
public void LogOn_Action_Invalid_Input_Test()
{
//** Arrange
_accountController.ModelState.AddModelError("UserName", "UserName is Required.");
//** Act
var actual = _accountController.LogOn(new LogOnModel(), string.Empty);
//** Assert
// verify ViewResult is returned from action
Assert.IsInstanceOfType(actual, typeof(ViewResult));
}
}
感謝您的鏈接 – StringBuilder