Модульный тест WebApi2, передающий значения заголовков

Я работаю над проектом, используя 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);
    }

Теперь я перехожу к чему-то немного сложному, где мне нужно получить доступ к значению из заголовка запроса.

Я создал свой собственный результат Ok(), расширив IHttpActionResult

   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.

Может ли кто-нибудь предложить хорошую статью или указать мне в правильном направлении?

Спасибо!!!


person Diver Dan    schedule 16.01.2014    source источник


Ответы (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)

См. здесь, как создать ParameterBindingAttribute

person LostInComputer    schedule 16.01.2014
comment
Спасибо @LostInComputer за подробный ответ. Я согласен, что контроллер API чувствует себя грязным местом для проверки заголовка. Однако часть нашего API потребует, чтобы пользователь предоставил идентификатор клиента в заголовке. Я думал об использовании фильтра действий, чтобы проверить это, однако я все еще хочу проверить это. customerid — это первое из примерно 4 значений, хранящихся в заголовке, который нам нужно протестировать. - person Diver Dan; 16.01.2014
comment
Мое предлагаемое решение: вы можете разделить тесты на два. 1: Проверить, что контроллеру предоставлен допустимый идентификатор клиента. 2. Проверить, что идентификатор клиента извлекается из заголовка с помощью привязки параметра. - person LostInComputer; 16.01.2014
comment
Надеюсь, мы получим еще один ответ от кого-то. Мне также любопытны другие идеи. - person LostInComputer; 16.01.2014