如何测试未调用服务方法

时间:2012-06-20 23:42:20

标签: asp.net-mvc unit-testing moq mstest data-annotations

我的同事和我正在讨论编写单元测试的正确方法,以确保用户在我们的ASP.NET MVC 2应用程序中将错误数据输入表单时收到错误。以下是我们过去所做的事情,从模型开始:

public class LoginModel
{
    public string Username { get; set; }
}

这是控制器动作:

[HttpPost]
public ActionResult Login( LoginModel loginModel )
{
    if ( loginModel.Username == null )
    {
        ModelState.AddModelError( "Username", "Username is required!" );

        return View( loginModel );
    }

    LoginService.Login( loginModel );
}

最后,这是测试方法:

[TestMethod]
public void Login_Post_Blank_Username_Displays_Error()
{
    var controller = GetHomeController();

    var loginModel = new LoginModel
    {
        Username = null
    };

    var result = controller.Login( loginModel );

    Assert.IsInstanceOfType( result, typeof( ViewResult ) );

    var view = (ViewResult)result;

    Assert.IsNotNull( view.ViewData.ModelState["Username"].Errors.First().ErrorMessage );
}

他向我指出,这不是为这种情况编写测试的正确方法。出于一个原因,它非常脆弱 - 将Username属性更改为其他任何内容都会破坏测试。其次,最好依靠DataAnnotations并测试控制器正在做什么。所以,我们的新模型看起来像这样:

public class LoginModel
{
    [Required( ErrorMessage = "Username is required!" )]
    public string Username { get; set; }
}

我们的控制器操作将改为:

[HttpPost]
public ActionResult Login( LoginModel loginModel )
{
    if ( !Model.IsValid() )
    {
        return View( loginModel );
    }

    LoginService.Login( loginModel );
}

问题来自于单元测试,它完全没有注意到DataAnnotations,因此测试失败了。我的同事说我们应该真正测试的是没有调用LoginService,但我不确定如何测试。他建议像这样使用Moq:

[TestMethod]
public void Login_Post_Blank_Username_Displays_Error()
{
    var controller = GetHomeController();

    var loginModel = new LoginModel
    {
        Username = null
    };

    loginServiceMock.Setup( x => x.Login( It.IsAny<LoginModel>() ) )
        .Callback( () => Assert.Fail( "Should not call LoginService if Username is blank!" ) );

    var result = controller.Login( loginModel );

    loginServiceMock.Verify();
}

您对此有何看法?测试服务方法没有被调用的正确方法是什么?如何在用户表单中测试错误数据的正确方法呢?

2 个答案:

答案 0 :(得分:3)

验证时:

loginServiceMock.Verify( x => x.Login( It.IsAny<LoginModel>() ), Times.Never() );

您应该删除模拟中的设置。 为了使代码在您的操作中输入if语句,您可以在调用控制器中的操作之前在ModelState中添加模型错误。

以下是您的代码的外观:

[TestMethod]
public void Login_Post_Blank_Username_Displays_Error()
{
    var controller = GetHomeController();

    var loginModel = new LoginModel
    {
        Username = null
    };

    controller.ModelState.AddModelError("a key", "a value");

    var result = controller.Login( loginModel );

    loginServiceMock.Verify( x => x.Login( It.IsAny<LoginModel>() ), Times.Never() );
}

答案 1 :(得分:1)

假设您的Mocked LoginService已通过属性注入设置,您可以按如下方式编写测试。

    [TestMethod]
    public void LoginPost_WhenUserNameIsNull_VerifyLoginMethodHasNotBeenCalled()
    {
        //Arrange
        var loginServiceMock = new Mock<ILoginService>();
        var sut = new HomeController { LoginService = loginServiceMock .Object};

        var loginModel = new LoginModel {
            Username = null
        };
        sut.ModelState.AddModelError("fakeKey", "fakeValue");

        //Act
        sut.Login(loginModel);

        //Verify
        loginServiceMock.Verify(x => x.Login(loginModel), Times.Never(), "Login method has been called.");
    }