Configure OData Test Server

497 Views Asked by At

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?

1

There are 1 best solutions below

0
On BEST ANSWER

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.