对于您当前的示例,我无法详细说明单元测试。因为你所得到的只有一个方法ShowDetails
,它返回一个View
与 绑定的UsersIdentificationViewModel
. 由于您只返回View
,因此您可以将返回类型更改为ViewResult
. 然后,您所能测试的就是与 绑定的模型View
是否为 type 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();
}
登录模型.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; }
}
您可以在操作中注意到许多if..else条件,LogOn
它们是单元测试的良好候选者。
我会为这个动作写至少四个单元测试。
- 验证输入有效凭据时,操作应重定向到传递的 url。
- 验证是否传递有效凭据,而不
returnUrl
应将AccountController
用户重定向到Home
操作。
- 验证传递无效凭据时,帐户控制器应返回带有错误的视图。
- 验证当存在验证错误时,控制器应返回带有错误的视图。
这是我使用MSTest
and编写的单元测试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));
}
}