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!