asp.net-coreodatatestserver

Configure OData Test Server


Trying to set-up unit / integration tests for some extensions I am writing for the OdataQueryOptions class. I am using .net core 3.1.

In order to create the SUT instance - I need a HttpRequest. Which I creating using the WebApplicationFactory

public class TestingWebApplicationFactoryFixture : WebApplicationFactory<TestStartUp>
{
    protected override IHostBuilder CreateHostBuilder()
    {
        var builder = Host.CreateDefaultBuilder();
        builder.ConfigureWebHost(hostBuilder =>
        {
            hostBuilder.ConfigureServices(services =>
            {
                services.AddMvc(options => options.EnableEndpointRouting = false);
                services.AddOData();
            }).Configure(app =>
            {
                app.UseMvc(routeBuilder =>
                {
                    routeBuilder.EnableDependencyInjection();
                    routeBuilder.Select().Expand().OrderBy().Filter().MaxTop(int.MaxValue);
                });
            });
        });
        return builder;
    }

I arrange the test to use the TestServer to produce the HttpContext. The OdataQueryContext and HttpRequest is then used to instantiate the OdataQueryOptions object.

        const string path = "/?$filter=SalesOrderID eq 43659";
        var httpContext = await _testingWebApplicationFactoryFixture.Server.SendAsync(context => 
        {
            context.Request.Method = HttpMethods.Get;
            context.Request.Path = path;
        });
        var modelBuilder = new ODataConventionModelBuilder();
        modelBuilder.AddEntityType(typeof(Customer));
        var model = modelBuilder.GetEdmModel();
        var odataQueryContext = new ODataQueryContext(model, typeof(Customer), new ODataPath());
        var sut = new ODataQueryOptions<Customer>(odataQueryContext, httpContext.Request);

I am getting an exception during the instantiation of the object:

System.ArgumentNullException
Value cannot be null. (Parameter 'provider')
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T] 
(IServiceProvider provider)
at Microsoft.AspNet.OData.Extensions.HttpRequestExtensions.CreateRequestScope(HttpRequest request, 
String routeName)
at Microsoft.AspNet.OData.Extensions.HttpRequestExtensions.CreateRequestContainer(HttpRequest 
request, String routeName)
at Microsoft.AspNet.OData.Extensions.HttpRequestExtensions.GetRequestContainer(HttpRequest request)
at Microsoft.AspNet.OData.Query.ODataQueryOptions..ctor(ODataQueryContext context, HttpRequest  
request)
at Microsoft.AspNet.OData.Query.ODataQueryOptions`1..ctor(ODataQueryContext context, HttpRequest 
request)

Digging into the actual method that is throwing - it is because the IServiceProvider is null. Shouldn't this be handled by the host?

UPDATE:

I modified the test method a bit so that I eliminate the WebApplicationFactory class.

Instead I create a TestServer with an IWebHostBuilder:

    private IWebHostBuilder GetBuilder()
    {
        var webHostBuilder = new WebHostBuilder();
            webHostBuilder
                .UseTestServer()
                .ConfigureServices(services =>
                {
                    services.AddMvc(options => options.EnableEndpointRouting = false);
                    services.AddOData();
                }).Configure(app =>
                {
                    app.UseMvc(routeBuilder =>
                    {
                        routeBuilder.EnableDependencyInjection();
                        routeBuilder.Select().Expand().OrderBy().Filter().MaxTop(int.MaxValue);
                    });
                });
            return webHostBuilder;
    }

And then create the TestServer:

    [Fact]
    public async Task QueryGenerator_Generate_SomeExpress_ShouldProduce()
    {
        const string path = "/?$filter=SalesOrderID eq 43659";
        var testServer = new TestServer(GetBuilder());
        var httpContext = await testServer.SendAsync(context =>
        {
            context.Request.Method = HttpMethods.Get;
            context.Request.Path = path;
        });
        var modelBuilder = new ODataConventionModelBuilder();
        modelBuilder.AddEntityType(typeof(Customer));
        var model = modelBuilder.GetEdmModel();
        var odataQueryContext = new ODataQueryContext(model, typeof(Customer), new ODataPath());
        var sut = new ODataQueryOptions<Customer>(odataQueryContext, httpContext.Request);
    }

I get the same exception. Why is the IServiceProvider null?


Solution

  • Never got a solution to using TestServer, but I found a work around. At the end of the day I needed the OdataQueryOptions class generated by the framework. So I created an IClassFixture<> in Xunit to manually get it created.

    public class OdataQueryOptionFixture
    {
        public IServiceProvider Provider { get; private set; }
        private IEdmModel _edmModel;
    
        public OdataQueryOptionFixture()
        {
            SetupFixture();
        }
    
    
        public ODataQueryOptions<T> CreateODataQueryOptions<T>(HttpRequest request)
            where T : class
        {
            var odataQueryContext = CreateOdataQueryContext<T>();
            var odataQueryOptions = new ODataQueryOptions<T>(odataQueryContext, request);
            return odataQueryOptions;
        }
    
        private ODataQueryContext CreateOdataQueryContext<T>()
            where T : class
        {
            var odataQueryContext = new ODataQueryContext(_edmModel, typeof(T), new ODataPath());
            return odataQueryContext;
        }
    
        private void SetupFixture()
        {
            var collection = new ServiceCollection();
            collection.AddOData();
            collection.AddTransient<ODataUriResolver>();
            collection.AddTransient<ODataQueryValidator>();
            Provider = collection.BuildServiceProvider();
            ConfigureRoutes();
            BuildModel();
        }
    
        private void ConfigureRoutes()
        {
            var routeBuilder = new RouteBuilder(Mock.Of<IApplicationBuilder>(x => x.ApplicationServices == Provider));
            routeBuilder.Select().Expand().OrderBy().Filter().MaxTop(int.MaxValue).Count();
            routeBuilder.EnableDependencyInjection();
        }
        private void BuildModel()
        {
            var edmContext = new AdventureWorksEdmContext();
            _edmModel = edmContext.BuildModel();
        }
    

    Using the class fixture in a test class to construct the OdataQueryOptions

            private QueryOptionsBuilder<Customer> GetSut(HttpRequest request)
        {
            var odataQueryOptions = _odataQueryOptionFixture.CreateODataQueryOptions<Customer>(request);
            var odataQuerySettings = new ODataQuerySettings();
            var odataValidationSettings = new ODataValidationSettings();
            var customerExpandBinder = new CustomerExpandBinder(odataValidationSettings, odataQueryOptions.SelectExpand);
            var customerOrderByBinder = new CustomerOrderByBinder(odataValidationSettings, odataQueryOptions.OrderBy);
            var customerSelectBinder = new CustomerSelectBinder(odataValidationSettings, odataQueryOptions.SelectExpand);
            var customerCompositeBinder = new CustomerCompositeBinder(customerExpandBinder, customerOrderByBinder, customerSelectBinder);
            return new QueryOptionsBuilder<Customer>(customerCompositeBinder, odataQuerySettings);
        }
    

    TestServer would have been easier - but this gets the job done.