0

I'm a bit lost in testing my custom modelbinder that parses a querystring. As the API is given and now must be migrated to .NET CORE, I am not able to change the syntax of the url query parameter. So I can not use the [FromQuery] attribute at the controllers parameter.

The controller is simple:

public class HostListController
    : ControllerBase
    {
        [HttpGet]
        public async Task<ActionResult<IList<SomeDto>>> Get(
             CustomQueryExpr filter,
             [FromQuery] bool pretty)
        {
            // do some fetch
        }
    }

And the custom model binder implementation is also straight forward:

public class CustomQueryBinder
      : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            var parser = new QueryParser();
            //--- get queryString from request
            var queryString = bindingContext.HttpContext.Request.QueryString;

            //--- parse string  
            var queryExpr = parser.Parse(queryString.Value);

            bindingContext.Result = ModelBindingResult.Success(queryExpr);
            return Task.CompletedTask;
        }
    }

The concrete implementation of the custom QueryParser is not important, it's method 'Parse' returns a 'CustomQueryExpr' instance in any case.

Now I'm struggling in assembling the test.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;

public class TestCustomQueryBinder
    {
        [Fact]
        public async Task TestValidQuery_BindModel_ReturnCustomQueryExpr()
        {
            //--- assemble
            var modelMetadata = new EmptyModelMetadataProvider();

            var requestFake = new HttpRequestFeature();
            requestFake.QueryString = "?filter_arg1=te*t&select=arg1,arg2";

            var features = new FeatureCollection();
            features.Set<IHttpRequestFeature>(requestFake);

            var fakeHttpContext = new DefaultHttpContext(features);

            var bindingContext = new DefaultModelBindingContext
            {
                ModelName = "CustomQueryExpr",
                ModelMetadata = modelMetadata.GetMetadataForType(typeof(CustomQueryExpr)),
                ActionContext = new ActionContext()
                {
                    HttpContext = fakeHttpContext
                }
            };

            var binder = new CustomQueryBinder();

            //--- act
            await binder.BindModelAsync(bindingContext);

            //--- assert
            Assert.NotNull(bindingContext.Result);
            Assert.True(bindingContext.Result.IsModelSet);
            Assert.True(bindingContext.Result.Model is CustomQueryExpr);
        }
    }

Works now as expected. Sometimes it helps to simply explain the problem to others to find the own mistake ... forrests and trees.

But I'm sure this solution can be improved, so please add your comments, hints and improvements to learn from.

desertnaut
  • 57,590
  • 26
  • 140
  • 166
Daiim
  • 1
  • 1
  • Are you dissatisfied with your code? But it seems to me that your code is a very common unit test. . . – Xinran Shen Mar 03 '22 at 02:23
  • The initial question was according how to setup the HttpContext in a DefaultModelBindingContext, as it is read-only. Then I recalled a way using the ActionContext that solved the problem. So I fixed the description/example to show the solution. But at the end - the test "knows" to much about the model binder implementation, I guess. So there might be better solutions, that I did not find. – Daiim Mar 03 '22 at 08:23

0 Answers0