【问题标题】:Unit test WebApi2 passing header values单元测试 WebApi2 传递标头值
【发布时间】:2014-02-04 19:52:51
【问题描述】:

我正在使用 WebApi2 开发一个项目。在我的测试项目中,我使用的是 Moq 和 XUnit。

到目前为止,测试一个 api 非常简单,可以像 GET 一样进行

  [Fact()]
    public void GetCustomer()
    {
        var id = 2;

        _customerMock.Setup(c => c.FindSingle(id))
            .Returns(FakeCustomers()
            .Single(cust => cust.Id == id));

        var result = new CustomersController(_customerMock.Object).Get(id);

        var negotiatedResult = result as OkContentActionResult<Customer>;
        Assert.NotNull(negotiatedResult);
        Assert.IsType<OkNegotiatedContentResult<Customer>>(negotiatedResult);
        Assert.Equal(negotiatedResult.Content.Id,id);
    }

现在我正在处理一些复杂的事情,我需要从请求标头中访问值。

我通过扩展 IHttpActionResult 创建了自己的 Ok() 结果

   public OkContentActionResult(T content,HttpRequestMessage request)
    {
        _request = request;
        _content = content;
    }

这允许我有一个从请求中读取标头值的小助手。

 public virtual IHttpActionResult Post(Customer customer)
    {
        var header = RequestHeader.GetHeaderValue("customerId", this.Request);

        if (header != "1234")

我是如何使用虚拟请求设置 Moq 的?

我花了大约最后一个小时寻找一个允许我使用 webapi 执行此操作的示例,但是我似乎找不到任何东西。

到目前为止......我很确定它的 api 是错误的,但我有

      // arrange
        var context = new Mock<HttpContextBase>();
        var request = new Mock<HttpRequestBase>();
        var headers = new NameValueCollection
        {
            { "customerId", "111111" }
        };
        request.Setup(x => x.Headers).Returns(headers);
        request.Setup(x => x.HttpMethod).Returns("GET");
        request.Setup(x => x.Url).Returns(new Uri("http://foo.com"));
        request.Setup(x => x.RawUrl).Returns("/foo");
        context.Setup(x => x.Request).Returns(request.Object);
        var controller = new Mock<ControllerBase>();
        _customerController = new CustomerController()
        {
            //  Request = request,

        };

我不太确定接下来我需要做什么,因为过去我不需要设置模拟 HttpRequestBase。

谁能推荐一篇好文章或指出正确的方向?

谢谢!!!

【问题讨论】:

    标签: asp.net-web-api moq xunit xunit.net asp.net-web-api2


    【解决方案1】:

    我认为您应该避免读取控制器中的标头,以便更好地分离关注点(您不需要从控制器中的请求正文中读取客户,对吧?)和可测试性。

    我将如何创建一个CustomerId 类(这是可选的。请参阅下面的注释)和CustomerIdParameterBinding

    public class CustomerId
    {
        public string Value { get; set; }
    }
    
    public class CustomerIdParameterBinding : HttpParameterBinding
    {
        public CustomerIdParameterBinding(HttpParameterDescriptor parameter) 
        : base(parameter)
        {
        }
    
        public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            actionContext.ActionArguments[Descriptor.ParameterName] = new CustomerId { Value = GetIdOrNull(actionContext) };
            return Task.FromResult(0);
        }
    
        private string GetIdOrNull(HttpActionContext actionContext)
        {
            IEnumerable<string> idValues;
            if(actionContext.Request.Headers.TryGetValues("customerId", out idValues))
            {
                return idValues.First();
            }
            return null;
        }
    }
    

    编写 CustomerIdParameterBinding

    config.ParameterBindingRules.Add(p =>
    {
        return p.ParameterType == typeof(CustomerId) ? new CustomerIdParameterBinding(p) : null;
    });
    

    然后在我的控制器中

    public void Post(CustomerId id, Customer customer)
    

    测试参数绑定

    public void TestMethod()
    {
        var parameterName = "TestParam";
        var expectedCustomerIdValue = "Yehey!";
    
        //Arrange
        var requestMessage = new HttpRequestMessage(HttpMethod.Post, "http://localhost/someUri");
        requestMessage.Headers.Add("customerId", expectedCustomerIdValue );
    
        var httpActionContext = new HttpActionContext
        {
            ControllerContext = new HttpControllerContext
            {
                Request = requestMessage
            }
        };
    
        var stubParameterDescriptor = new Mock<HttpParameterDescriptor>();
        stubParameterDescriptor.SetupGet(i => i.ParameterName).Returns(parameterName);
    
        //Act
        var customerIdParameterBinding = new CustomerIdParameterBinding(stubParameterDescriptor.Object);
        customerIdParameterBinding.ExecuteBindingAsync(null, httpActionContext, (new CancellationTokenSource()).Token).Wait();
    
        //Assert here
        //httpActionContext.ActionArguments[parameterName] contains the CustomerId
    }
    

    注意:如果您不想创建CustomerId 类,可以使用自定义ParameterBindingAttribute 注释您的参数。像这样

    public void Post([CustomerId] string customerId, Customer customer)
    

    See here on how to create a ParameterBindingAttribute

    【讨论】:

    • 感谢@LostInComputer 的详细回复。我同意 api 控制器感觉验证标头的地方很脏。然而,我们的 api 的一部分将要求用户在标题中提供一个 customerid。我正在考虑使用 actionfilter 来验证这一点,但是我仍然渴望能够对此进行测试。 customerid 是我们需要测试的标头中保留的大约 4 个值中的第一个。
    • 我建议的解决方案是您可以将测试分成两个。 1:测试是否向控制器提供了有效的customerId 2.测试是否通过参数绑定从header中检索到customerId。
    • 希望我们能从某人那里得到另一个答案。我也对其他想法很好奇。
    • 当您将参数绑定规则添加到 httpconfiguration 时,您提供了一个 lambda 函数,该函数符合文档 docs.microsoft.com/en-us/previous-versions/aspnet/… 中的定义。但是,没有需要提供的类型吗?然而,编译器只是免费通行证。为什么?
    猜你喜欢
    • 2017-08-13
    • 2014-06-23
    • 1970-01-01
    • 2018-11-26
    • 2021-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多