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?
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.