Introduction

When you are trying to unit test classes and they rely IOptions<T> you likely start having to look at workarounds to get these setup. As IOptions<T> is an interface the first thought might be “ah, I’ll mock it” and this a potential. However in this post I will show you have you can get the full functionality of the IOptions pattern without mocking.

Problem

So you have a service which relies on an instance of IOptions<T> to be passed in through constructor injection. You have the dependency injection constructs setup in your .NET application and at run time it is being resolved as expected and all is well.

public class TestService
{
    private readonly IOptions<TestOptions> _options;

    public TestService(IOptions<TestOptions> options)
    {
        _options = options;
    }

    public string Name => _options.Value.Name;
}

public class TestOptions
{
    public string Name { get; set; } = null!;
}

I will be using the above examples of options and service classes to aid with explainations.

However when you come to writing your unit tests for the service we come across a snag. How do we get the dependencies resolved? Do we manually create an implementation? Do we mock the interface and pass it in? What happens if our system under test (sut) is created for you through AutoFixture? Let’s take a look!

Option #1 - Manual Implementation

One option, which I would not recommend, is to create your own implementation.

public class MyOptions<TOptions> : IOptions<TOptions> where TOptions : class
{
    public TOptions Value { get; }
}

You can create a generic version, like the above, or very specific versions for your use case. However this is added overhead, lots of boilerplate code and to be honest who wants to spend time maintaining this type of code when you have functionality to write etc.?

Let’s move on quickly!

Option #2 - Mocking

You can use your preferred mocking framework but the example below is using Moq. This makes the IOptions<T> interface behave the same as any other interface which is being mocked. You might feel like you’ve lost control of the “options” functionality.

[Fact]
public void UsingMock()
{
    // Arrange
    var mock = new Mock<IOptions<TestOptions>>();
    mock.Setup(x => x.Value).Returns(new TestOptions { Name = "Adam" });
    var sut = new TestService(mock.Object);
    
    // Act
    var result = sut.Name;
    
    // Assert
    result.Should().Be("Adam");
}

In this example you have the same setup as you’ve expect on a mock. In the example of using IOptions<T> this doesn’t feel right for me. So how else can we do it?

Option #3 - Options Helper Class

In the Microsoft.Extensions.Options namespace there is an Options helper class which allows for easy creation of instances at runtime which implement IOptions<T>. This in fact is very similar to how the underlying framework provides the information. Obviously we can’t instantiate an interface so it creates an instance of OptionsWrapper<T> instead. When looking at the code this is actually the same as our own implementation of IOptions<T> above but without the overhead of maintaining it ourselves. Nice!

[Fact]
public void OptionsCreate()
{
    // Arrange
    var options = Options.Create(new TestOptions { Name = "Adam" });
    var sut = new TestService(options);

    // Act
    var result = sut.Name;

    // Assert
    result.Should().Be("Adam");
}

This isn’t too bad to be honest; it’s clean, to the point and easy to maintain.

The biggest issue I have with all the approaches so far is we are still creating the system under test manually. This means if we are refactoring it and adding/removing any dependencies it won’t compile, we’ll possibly end up commenting out the unit tests and then ignoring them etc. and the spiral of test exclusion will iterate.

Option 4 - AutoFixture & Mocking

There are loads of blog posts about Autofixture out in the internet and I have written a couple myself. It’s an awesome library and super useful for when you are writing your tests. So how can we use it?

With minimal code change from the above examples, and referencing the required packages …

<PackageReference Include="AutoFixture.AutoMoq" Version="4.18.1" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.18.1" />

… we can set it up with minimal fuss.

[Fact]
public void WithAutoFixture_WithMoq()
{
    // Arrange
    var fixture = new Fixture();
    fixture.Customize(new AutoMoqCustomization());
    var options = fixture.Freeze<IOptions<TestOptions>>();
    var sut = fixture.Create<TestService>();
    
    // Act
    var result = sut.Name;

    // Assert
    result.Should().Be(options.Value.Name);
}

We are adding in the main customization which allows for using Moq under the hood when we are asking for interfaces of things. We are then freezing the required options requested which will return the Mock<IOptions<TestOptions>> which can then be referenced. When you request to “freeze” a type from AutoFixture what this does is create an instance and then use that for the remaining duration of the request. If something else requires that type it will use that instance instead of creating a new one. In this example as we are creating a mock it will inject that as required.

Again, this is relying on mocking and I think for this scenarios the Options.Create constructs are preferrable.

Option 5 - AutoFixture and Bespoke Customization

Autofixture gives the ability to add customizations, which I have written about before, through the power of specimen builders. What these do is allow for specific types and scenarios to be handled in specific ways.

We can do this for IOptions<T> and through some reflection and type manipulation we can create an instance of what we require.

First off we need to determine whether it’s the request we are looking for to intercept.

       if (request is Type type 
           && type.IsGenericType 
           && type.GetGenericTypeDefinition() == typeof(IOptions<>))

As specimen builders can get nearly any kind of request coming in through the Create method we first need to check whether the request is a Type. If not return a return new NoSpecimen(); to allow for the processing to move on.

Once we know we are looking at the expected scenario we need to create an instance of the required options type. As this has to be restricted to a class by the interface definition we can resolve an instance through the context.

var typeOfOptions = type.GetGenericArguments()[0];

var instance = context.Resolve(typeOfOptions);

Once that is in place we need to create the IOptions<> wrapper for it. This is where we need to do some reflection with the options create helper method. Due to the way the instance is created we don’t know what the type is so when the instance is passed into Options.Create it will return an IOptions<object> which is not what we want.

To get around this we need to use reflection to specify the type of the method for a generic parameter. This is done by getting the method of the target type and then as it’s a generic method we need to call MakeGenericMethod with the type to force the typing. Once the method is defined it can be invoked with the instance of the target type.

var optionsCreateMethod = typeof(Options).GetMethod(nameof(Options.Create))?.MakeGenericMethod(typeOfOptions);

var optionsInstance = optionsCreateMethod?.Invoke(null, new[] { instance });

Now we have the specimen builder defined we can use it in our tests.

[Fact]
public void WithAutoFixture_WithOptionsSpecification()
{
    // Arrange
    var fixture = new Fixture();
    fixture.Customizations.Add(new OptionsSpecificationBuilder());
    var options = fixture.Freeze<IOptions<TestOptions>>();
    var sut = fixture.Create<TestService>();

    // Act
    var result = sut.Name;

    // Assert
    result.Should().Be(options.Value.Name);
}

Full Implementation of SpecimenBuilder

This is the full specimen builder implementation with comments.

public class OptionsSpecificationBuilder : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        // determine if it's what we are looking for
        if (request is Type type 
            && type.IsGenericType 
            && type.GetGenericTypeDefinition() == typeof(IOptions<>))
        {
            // determine the type of options you want to create
            var typeOfOptions = type.GetGenericArguments()[0];

            // get autofixture to resolve an instance for you using the default creation routines
            var instance = context.Resolve(typeOfOptions);
            
            // determine the type and through generics get access to the generic version of the method. You can't call it directly otherwise
            // at runtime it behaves like it is an IOptions<object> which is not what you are looking for.
            var optionsCreateMethod = typeof(Options).GetMethod(nameof(Options.Create))?.MakeGenericMethod(typeOfOptions);

            // call the method, if available, with the instance resolved above. This is equivalent to Options.Create<typeOfOptions>(instance)
            var optionsInstance = optionsCreateMethod?.Invoke(null, new[] { instance });

            // return if possible, otherwise return no specimen
            return optionsInstance ?? new NoSpecimen();
        }

        return new NoSpecimen();
    }
}

Option 6 - AutoFixture, Bespoke Customization and DataAttributes

Now we’ve got all the infrastructure in place we can work how we want to. However there is still some boiler plate code with regards to Fixture setup etc. needing to be done. Using the DataAttribute extension points for AutoFixture and xUnit combined we can do some cool stuff. Now I’ve been using the same base InlineAutoMoqDataAttribute definition for years. This is an extension of the base inline data attribute as well as the auto moq functionality which mimics the code we’ve seen so far.

public class InlineAutoMoqDataAttribute : InlineAutoDataAttribute
{
    public InlineAutoMoqDataAttribute(params object[] values)
        : base(new AutoMoqDataAttribute(), values)
    {
    }

    private class AutoMoqDataAttribute : AutoDataAttribute
    {
        public AutoMoqDataAttribute()
            : base(() =>
            {
                var fixture = new Fixture();
                fixture.Customize(new AutoMoqCustomization());
                fixture.Customizations.Add(new OptionsSpecificationBuilder());
                return fixture;
            })
        {
        }
    }
}

As you can see from above the customization of the Fixure looks familiar with the earlier versions. This is in one place now and not needing to be repeated.

Converting the test scenario to be an xUnit [Theory] we can use the InlineAutoMoqData attribute and ask the framework to provide access to the information we want and also generate the SUT for us.

[Theory]
[InlineAutoMoqData]
public void WithAutoFixture_WithOptions_Inline(
    [Frozen] IOptions<TestOptions> options,
    TestService sut
    )
{
    // Arrange

    // Act
    var result = sut.Name;

    // Assert
    result.Should().Be(options.Value.Name);
}

The benefits of this route is we have moved the boiler plate code to one place for all test scenarios so we’ve reduced the code. In removing the boiler plate code we have also improved readability of the test scenario so we can work on the testing and not the setup infrastructure.

Note we are still using the freezing functionality but this is now done through the [Frozen] attribute which tells the framework to do the same thing as the Freeze method.

Conclusion

In this post we have reviewed the way to work with the IOptions<T> pattern in unit tests and how we can use AutoFixture to do the heavy loading. We have evaluated the different ways of how we can do it with AutoFixture resulting in extending the Inline attribute function to aid with adding different scenarios.

Thank you for getting this far. This post was quite a long one and I appreciate you taking the time to read it, I hope it helps.

Until next time!