Alex Gordon
Alex Gordon

Reputation: 60811

Unit testing service that requires ODataQueryOptions

In attempts to do some test-driven-development, I've created the most basic, buildable method:

public class NoteService : INoteService
{
    public IEnumerable<Annotation> GetNotes(ODataQueryOptions oDataQueryOptions)
    {
        return new List<Annotation>();
    }
}

When trying to unit test it, it seems impossible to create an instance of ODataQueryOptions:

[TestFixture]
public class NoteServiceTests
{
    [Test]
    public void GetNotes_Returns_IEnumerable_Of_Notes()
    {
        var sut = new NoteService();
        var queryOptions = new ODataQueryOptions(new ODataQueryContext(new EdmCoreModel(), new EdmCollectionType())// new new new etc??
                    Assert.That(() => sut.GetNotes(options), Is.InstanceOf<IEnumerable<Annotation>>());

    }
}

How do you create a simple instance of the object ODataQueryOptions in order to inject it for unit tests?

Upvotes: 2

Views: 1935

Answers (2)

Braden W
Braden W

Reputation: 91

I wanted to do something similar and found a workable solution:

My use case

I have a web service that passes OData query parameters down to our CosmosDB document client which translates them into a CosmosDB SQL query.

I wanted a way to write integration tests directly on the CosmosDB client without having to make outgoing calls to other downstream services.

Approaches

  • Tried mocking ODataQueryParameters using Moq but because it's a class and not an interface, Moq can't properly instantiate all of the properties that I need
  • Tried instantiating one directly, but outside of an MVC application this is extremely difficult to build the required EdmModel.
    • Wondered if there is a way to do it without constructing an EdmModel?
  • I finally figured out a way to do it with the following: This may not solve every use case, but here's the solution that I landed on for my needs:
public static class ODataQueryOptionsBuilder
    {
        private static WebApplicationFactory<TEntryPoint> _app =
            new WebApplicationFactory<TEntryPoint>();
        
        public static ODataQueryOptions<T> Build<T>(
            string queryString) 
            where T : class
        {
            var httpContext = new DefaultHttpContext();
            httpContext.Request.QueryString = new QueryString(queryString);
            httpContext.RequestServices = _app.Services;
            var modelBuilder = new ODataConventionModelBuilder();
            modelBuilder.EntityType<T>();
            var model = modelBuilder.GetEdmModel();
            var context = new ODataQueryContext(
                model, 
                typeof(T), 
                new Microsoft.AspNet.OData.Routing.ODataPath());
            return new ODataQueryOptions<T>(context, httpContext.Request);
        }
    }

Conclusion

I realized that all I needed to get the ODataQueryOptions was a test server that was set up exactly like my web service. Once I realized that, the solution was simple. Use WebApplicationFactory to create the test server, and then allow OData to use the IServiceProvider from that application to build the ODataQueryOptions that I needed.

Drawbacks

Realize that this solution is as performant as your TEntryPoint, so if your application takes a long time to start up, this will cause unit/integration tests to take a long time to run.

Upvotes: 0

Alex Gordon
Alex Gordon

Reputation: 60811

Will this work?

var request = new HttpRequestMessage(HttpMethod.Get, "");

var context = new ODataQueryContext(EdmCoreModel.Instance, typeof(int));

var options = new ODataQueryOptions(context, request);

Upvotes: 2

Related Questions