单元测试自定义验证过滤器

时间:2014-11-25 10:42:24

标签: unit-testing asp.net-web-api nunit moq

我有以下属性:

public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ModelState.IsValid == false)
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }  

我有这个通用的扩展方法,用于确定属性是否应用于方法

public static bool ActionHasFilter(this ApiController controller, string action, Type filter)
    {
        var controllerType = controller.GetType();
        var method = controllerType.GetMethod(action);

        object[] filters = method.GetCustomAttributes(filter, true);

        return filters.Any(x=>x.GetType() == filter);

    }

我的问题是如何在不测试控制器操作的情况下测试属性是否实际工作?

我们说我有以下实体

public class UserViewModel
{
     [Required]
     public string Name {get; set;}
     [Required]
     [EmailAddress]
     public string Email {get;set;
}

我如何模拟上下文并检查模型是否有效?

我正在使用Nunit和Moq。

2 个答案:

答案 0 :(得分:8)

Spock's solution处于正确的轨道上,但对代码来说有点过于干扰,因为它使ValidateModelAttribute类依赖于TestableHttpActionContext。我的实现使用一个公共属性,用于为单元测试“注入”Request对象,而实现为属性继续使用Request中的ActionContext对象: / p>

public class ValidateModelAttribute : ActionFilterAttribute
{
    public HttpRequestMessage TestRequestMessage { get; set; }

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        PerformValidation(actionContext, TestRequestMessage ?? actionContext.Request);
    }

    private void PerformValidation(HttpActionContext actionContext, HttpRequestMessage request)
    {
        if (actionContext.ModelState.IsValid == false)
        {
            actionContext.Response = request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
        }
    }
}

单元测试:

[Test]
public void OnActionExecuting_ValidModel_ResponseIsNotSet()
{
    var actionContext = new HttpActionContext();

    actionContext.ModelState.Clear();

    var attribute = new ValidateModelAttribute { TestRequestMessage = new HttpRequestMessage() };

    attribute.OnActionExecuting(actionContext);

    Assert.IsNull(actionContext.Response);
}

[Test]
public void OnActionExecuting_InvalidModel_ResponseIsSetToBadRequest()
{
    var actionContext = new HttpActionContext();

    actionContext.ModelState.AddModelError("key", "error");

    var attribute = new ValidateModelAttribute() { TestRequestMessage = new HttpRequestMessage() };

    attribute.OnActionExecuting(actionContext);

    Assert.AreEqual(HttpStatusCode.BadRequest, actionContext.Response.StatusCode);
}

请注意,我没有使用实际模型来验证ModelState,因为这超出了单元测试的范围:我们想测试ModelState的结果,而不是{ {1}}本身。 ; - )

答案 1 :(得分:0)

您可以按照以下方式执行此操作。 你需要考虑几件事。

  1. ModelState不需要被删除,只需添加模型错误,导致 actionContext.ModelState.IsValid 为false。

    contextStub.Object.ModelState.AddModelError(“key”,“error”);

    此外,您不需要 UserViewModel

  2. 由于Request属性是非虚拟的,因此无法使用Moq设置actionContext.Request。它也有一个getter,你也不能提供自己的HttpRequestMessage。

  3. 要解决此问题,您需要抽象HttpActionContext并创建Virtual属性,以便Moq可以提供设置。一种技术可以称为提取覆盖

    public class TestableHttpActionContext : HttpActionContext
    {
        public virtual new HttpRequestMessage  Request  { get; set; }
    }
    

    现在你的行动方法可以修改如下。

    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {            
            TestableActionExecuting((TestableHttpActionContext)actionContext);
        }
    
        private void TestableActionExecuting(TestableHttpActionContext actionContext)
        {
            if (actionContext.ModelState.IsValid == false)
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }
    

    现在你可以单独测试OnActionExecuting,如下所示。

    [TestFixture]
    public class UnitTest1
    {
        [Test]
        public void OnActionExecuting_ErrorResponse_ExpectBadRequest()
        {
            var sut = new ValidateModelAttribute();
            var contextStub = new Mock<TestableHttpActionContext>();
            contextStub.Object.ModelState.AddModelError("key", "error");
            contextStub.Setup(x => x.Request).Returns(new HttpRequestMessage());
    
            sut.OnActionExecuting(contextStub.Object);
    
            Assert.AreEqual("BadRequest", 
                             contextStub.Object.Response.StatusCode.ToString());
        }
    }