質問

I am working on a project using WebApi2. With my test project I am using Moq and XUnit.

So far testing an api has been pretty straight forward to do a GET like

  [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);
    }

Now I am moving onto something a little complicated where I need to access value from the request header.

I have created my own Ok() result by extending the IHttpActionResult

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

This allows me to have a small helper that reads the header value from the request.

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

        if (header != "1234")

How am I meant to setup Moq with a dummy Request?

I have spent the last hour or so hunting for an example that allows me to do this with webapi however I cant seem to find anything.

So far.....and I am pretty sure its wrong for the api but I have

      // 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,

        };

I am not really sure what next I need to do as I havent needed to setup a mock HttpRequestBase in the past.

Can anyone suggest a good article or point me in the right direction?

Thank you!!!

役に立ちましたか?

解決

I believe that you should avoid reading the headers in your controller for better separation of concerns (you don't need to read the Customer from request body in the controller right?) and testability.

How I will do it is create a CustomerId class (this is optional. see note below) and 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;
    }
}

Writing up the CustomerIdParameterBinding

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

Then in my controller

public void Post(CustomerId id, Customer customer)

Testing the Parameter Binding

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
}

Note: If you don't want to create a CustomerId class, you can annotate your parameter with a custom ParameterBindingAttribute. Like so

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

See here on how to create a ParameterBindingAttribute

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top