Create IOptions<T> for Unit Testing

The Option pattern uses classes to bind and read configuration for application settings no matter where they come, from appsettings.json, Environment variables, or any other providers (of course the order of them matters!).

By using the Options pattern, actually, you can get the benefits of these two:

- Encapsulation: Your services get whatever they need and no need the inject IConfiguration everywhere.

- Separation of Concerns: Creates different classes for different parts of the application, which means you're separating them from the setting point of view.

Imagine you have this section in the appsettings:

{
  "Config": {
    "Message": "Hello from the setting file!"
  }
}

For binding that configuration you need to have this class:

public class SampleOptions
{
    public string? Message { get; set; }
}

And simply bind it:

var builder = Host.CreateApplicationBuilder();

var configuration = builder.Configuration;
builder.Services.Configure<SampleOptions>(configuration.GetSection("Config"));

By calling Configure<TOptions> extension method on IServiceCollection interface, it will bind the section to your model, in fact, it will register IOptions<SampleOptions> into DI as a Singleton object.

Now it's time to inject your config model into any service you want, let's assume this is our service:

public class SampleService
{
    private readonly IOptions<SampleOptions> options;

    public SampleService(IOptions<SampleOptions> options)
    {
        this.options = options;
    }

    public string? GetMessage()
    {
        return options.Value.Message;
    }
}

It is working just fine, but the point of this blog is about how we can write a unit test for this class when we don't have Dependency Injection because we just mentioned the IOptions<SampleOptions> will be created by calling Configure method!

No worries, always there is a way!

There is a built-in class called Options under Microsoft.Extensions.Options namespace:

namespace Microsoft.Extensions.Options
{
    /// <summary>
    /// Helper class.
    /// </summary>
    public static class Options
    {
        // By default, we're going to keep public, parameterless constructor on any Options class.
        internal const DynamicallyAccessedMemberTypes DynamicallyAccessedMembers = DynamicallyAccessedMemberTypes.PublicParameterlessConstructor;

        /// <summary>
        /// The default name used for options instances: "".
        /// </summary>
        public static readonly string DefaultName = string.Empty;

        /// <summary>
        /// Creates a wrapper around an instance of <typeparamref name="TOptions"/> to return itself as an <see cref="IOptions{TOptions}"/>.
        /// </summary>
        /// <typeparam name="TOptions">Options type.</typeparam>
        /// <param name="options">Options object.</param>
        /// <returns>Wrapped options object.</returns>
        public static IOptions<TOptions> Create<[DynamicallyAccessedMembers(DynamicallyAccessedMembers)] TOptions>(TOptions options)
            where TOptions : class
        {
            return new OptionsWrapper<TOptions>(options);
        }
    }
}

Just see the Create method, by calling it creates an IOptions object for us which is all we need, right?

Let's create the unit test for the service:

public class SampleServiceTest
{
    [Fact]
    public void CallGetMessageMethod()
    {
        IOptions<SampleOptions> options = Options.Create(new SampleOptions
        {
            Message = "Hello, World!"
        });

        var service = new SampleService(options);

        var message = service.GetMessage();

        message.Should().NotBeNull();
        message.Should().Be("Hello, World!");
    }
}

Nice! The test will pass.

Although in Integration Test you may not need it still for unit testing is useful.

Here is the repo to check the code: github

Code Simple!

10
An error has occurred. This application may no longer respond until reloaded. Reload x