342

I feel like I'm missing something really obvious here. I have classes that require injecting of options using the .NET Core IOptions pattern(?). When I unit test that class, I want to mock various versions of the options to validate the functionality of the class. Does anyone know how to correctly mock/instantiate/populate IOptions<T> outside of the Startup class?

Here are some samples of the classes I'm working with:

Settings/Options Model

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace OptionsSample.Models
{
    public class SampleOptions
    {
        public string FirstSetting { get; set; }
        public int SecondSetting { get; set; }
    }
}

Class to be tested which uses the Settings:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using OptionsSample.Models
using System.Net.Http;
using Microsoft.Extensions.Options;
using System.IO;
using Microsoft.AspNetCore.Http;
using System.Xml.Linq;
using Newtonsoft.Json;
using System.Dynamic;
using Microsoft.Extensions.Logging;

namespace OptionsSample.Repositories
{
    public class SampleRepo : ISampleRepo
    {
        private SampleOptions _options;
        private ILogger<AzureStorageQueuePassthru> _logger;

        public SampleRepo(IOptions<SampleOptions> options)
        {
            _options = options.Value;
        }

        public async Task Get()
        {
        }
    }
}

Unit test in a different assembly from the other classes:

using OptionsSample.Repositories;
using OptionsSample.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;

namespace OptionsSample.Repositories.Tests
{
    public class SampleRepoTests
    {
        private IOptions<SampleOptions> _options;
        private SampleRepo _sampleRepo;


        public SampleRepoTests()
        {
            //Not sure how to populate IOptions<SampleOptions> here
            _options = options;

            _sampleRepo = new SampleRepo(_options);
        }
    }
}
1
  • Are you confusing the meaning of mocking? You mock on an interface and configure it to return a specified value. For IOptions<T>you only have to mock Value to return the class you desire Commented Nov 29, 2016 at 22:00

12 Answers 12

618

You need to manually create and populate an IOptions<SampleOptions> object. You can do so via the Microsoft.Extensions.Options.Options helper class. For example:

IOptions<SampleOptions> someOptions = Options.Create<SampleOptions>(new SampleOptions());

You can simplify that a bit to:

var someOptions = Options.Create(new SampleOptions());

Obviously this isn't very useful as is. You'll need to actually create and populate a SampleOptions object and pass that into the Create method.

Sign up to request clarification or add additional context in comments.

6 Comments

I appreciate all the additional answers that show how to use Moq, etc., but this answer is so simple that it's definitely the one I am using. And it works great!
Great answer. Far simpler that relying on a mocking framework.
thanks. I was so tired of writing new OptionsWrapper<SampleOptions>(new SampleOptions()); everywhere
@BritishDeveloper better var someOptions = Options.Create(new SampleOptions()); ?
I'm in .Net 7 and am currently getting told that The type name 'Create' does not exist in the type 'Options' FACE. PALM.
Make sure you are referencing Microsoft.Extensions.Options.Options when calling Create() - I got this message too but it was trying to reference an Options namespace within my solution.
101

If you intent to use the Mocking Framework as indicated by @TSeng in the comment, you need to add the following dependency in your project.json file.

   "Moq": "4.6.38-alpha",

Once the dependency is restored, using the MOQ framework is as simple as creating an instance of the SampleOptions class and then as mentioned assign it to the Value.

Here is a code outline how it would look.

SampleOptions app = new SampleOptions(){Title="New Website Title Mocked"}; // Sample property
// Make sure you include using Moq;
var mock = new Mock<IOptions<SampleOptions>>();
// We need to set the Value of IOptions to be the SampleOptions Class
mock.Setup(ap => ap.Value).Returns(app);

Once the mock is setup, you can now pass the mock object to the contructor as

SampleRepo sr = new SampleRepo(mock.Object);   

HTH.

FYI I have a git repository that outlines these 2 approaches on Github/patvin80

1 Comment

Really wish this worked for me, but it doesn't :( Moq 4.13.1
45

You can avoid using MOQ at all. Use in your tests .json configuration file. One file for many test class files. It will be fine to use ConfigurationBuilder in this case.

Example of appsetting.json

{
    "someService" {
        "someProp": "someValue
    }
}

Example of settings mapping class:

public class SomeServiceConfiguration
{
     public string SomeProp { get; set; }
}

Example of service which is needed to test:

public class SomeService
{
    public SomeService(IOptions<SomeServiceConfiguration> config)
    {
        _config = config ?? throw new ArgumentNullException(nameof(_config));
    }
}

NUnit test class:

[TestFixture]
public class SomeServiceTests
{

    private IOptions<SomeServiceConfiguration> _config;
    private SomeService _service;

    [OneTimeSetUp]
    public void GlobalPrepare()
    {
         var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", false)
            .Build();

        _config = Options.Create(configuration.GetSection("someService").Get<SomeServiceConfiguration>());
    }

    [SetUp]
    public void PerTestPrepare()
    {
        _service = new SomeService(_config);
    }
}

6 Comments

This worked nicely for me, cheers! Didn't want to use Moq for something that seemed so simple and didn't want to try populate my own options with configuration settings.
Works great, but the vital missing piece of information is that you need to include the Microsoft.Extensions.Configuration.Binder nuget package, otherwise you don't get the "Get<SomeServiceConfiguration>" extension method available.
I had to run dotnet add package Microsoft.Extensions.Configuration.Json to get this to work. Great answer!
I had also to change the properties of the appsettings.json file to use the file in the bin file, as Directory.GetCurrentDirectory() was returning the content of the bin file. In "Copy to output directory" of appsettings.json, I set the value to "Copy if newer".
Using MSTest unit testing?
|
29

You can always create your options via Options.Create() and than simply use AutoMocker.Use(options) before actually creating the mocked instance of the repository you're testing. Using AutoMocker.CreateInstance<>() makes it easier to create instances without manually passing parameters

I've changed you're SampleRepo a bit in order to be able to reproduce the behavior I think you want to achieve.

public class SampleRepoTests
{
    private readonly AutoMocker _mocker = new AutoMocker();
    private readonly ISampleRepo _sampleRepo;

    private readonly IOptions<SampleOptions> _options = Options.Create(new SampleOptions()
        {FirstSetting = "firstSetting"});

    public SampleRepoTests()
    {
        _mocker.Use(_options);
        _sampleRepo = _mocker.CreateInstance<SampleRepo>();
    }

    [Fact]
    public void Test_Options_Injected()
    {
        var firstSetting = _sampleRepo.GetFirstSetting();
        Assert.True(firstSetting == "firstSetting");
    }
}

public class SampleRepo : ISampleRepo
{
    private SampleOptions _options;

    public SampleRepo(IOptions<SampleOptions> options)
    {
        _options = options.Value;
    }

    public string GetFirstSetting()
    {
        return _options.FirstSetting;
    }
}

public interface ISampleRepo
{
    string GetFirstSetting();
}

public class SampleOptions
{
    public string FirstSetting { get; set; }
}

Comments

29

Using the Microsoft.Extensions.Options.Options class:

var someOptions= Options.Create(new SampleOptions(){Field1="Value1",Field2="Value2"});

OR

var someOptions= Options.Create(new SampleOptions{Field1="Value1",Field2="Value2"});

1 Comment

This answer is a good addendum to Necoras' highly, up-voted answer since it adds the curly brace object initialization.
17

Given class Person that depends on PersonSettings as follows:

public class PersonSettings
{
    public string Name;
}

public class Person
{
    PersonSettings _settings;

    public Person(IOptions<PersonSettings> settings)
    {
        _settings = settings.Value;
    }

    public string Name => _settings.Name;
}

IOptions<PersonSettings> can be mocked and Person can be tested as follows:

[TestFixture]
public class Test
{
    ServiceProvider _provider;

    [OneTimeSetUp]
    public void Setup()
    {
        var services = new ServiceCollection();
        // mock PersonSettings
        services.AddTransient<IOptions<PersonSettings>>(
            provider => Options.Create<PersonSettings>(new PersonSettings
            {
                Name = "Matt"
            }));
        _provider = services.BuildServiceProvider();
    }

    [Test]
    public void TestName()
    {
        IOptions<PersonSettings> options = _provider.GetService<IOptions<PersonSettings>>();
        Assert.IsNotNull(options, "options could not be created");

        Person person = new Person(options);
        Assert.IsTrue(person.Name == "Matt", "person is not Matt");    
    }
}

To inject IOptions<PersonSettings> into Person instead of passing it explicitly to the ctor, use this code:

[TestFixture]
public class Test
{
    ServiceProvider _provider;

    [OneTimeSetUp]
    public void Setup()
    {
        var services = new ServiceCollection();
        services.AddTransient<IOptions<PersonSettings>>(
            provider => Options.Create<PersonSettings>(new PersonSettings
            {
                Name = "Matt"
            }));
        services.AddTransient<Person>();
        _provider = services.BuildServiceProvider();
    }

    [Test]
    public void TestName()
    {
        Person person = _provider.GetService<Person>();
        Assert.IsNotNull(person, "person could not be created");

        Assert.IsTrue(person.Name == "Matt", "person is not Matt");
    }
}

2 Comments

You aren't testing anything useful. The framework for DI my Microsoft is already unit tested. As it stands this is really an integration test (integration with a 3rd party framework).
@ErikPhilips My code shows how to mock IOptions<T> as requested by the OP. I agree that it does not test anything useful in itself, but it can be useful testing something else.
11

Here's another easy way that doesn't need Mock, but instead uses the OptionsWrapper:

var myAppSettingsOptions = new MyAppSettingsOptions();
appSettingsOptions.MyObjects = new MyObject[]{new MyObject(){MyProp1 = "one", MyProp2 = "two", }};
var optionsWrapper = new OptionsWrapper<MyAppSettingsOptions>(myAppSettingsOptions );
var myClassToTest = new MyClassToTest(optionsWrapper);

Comments

2

For my system and integration tests I prefer to have a copy/link of my config file inside the test project. And then I use the ConfigurationBuilder to get the options.

using System.Linq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace SomeProject.Test
{
public static class TestEnvironment
{
    private static object configLock = new object();

    public static ServiceProvider ServiceProvider { get; private set; }
    public static T GetOption<T>()
    {
        lock (configLock)
        {
            if (ServiceProvider != null) return (T)ServiceProvider.GetServices(typeof(T)).First();

            var builder = new ConfigurationBuilder()
                .AddJsonFile("config/appsettings.json", optional: false, reloadOnChange: true)
                .AddEnvironmentVariables();
            var configuration = builder.Build();
            var services = new ServiceCollection();
            services.AddOptions();

            services.Configure<ProductOptions>(configuration.GetSection("Products"));
            services.Configure<MonitoringOptions>(configuration.GetSection("Monitoring"));
            services.Configure<WcfServiceOptions>(configuration.GetSection("Services"));
            ServiceProvider = services.BuildServiceProvider();
            return (T)ServiceProvider.GetServices(typeof(T)).First();
        }
    }
}
}

This way I can use the config everywhere inside of my TestProject. For unit tests I prefer to use MOQ like patvin80 described.

Comments

2

Here is the sample code to test the IOptions and IOptionsMonitor using

  • Interface (Convert IOptions to IConfigurationClass and then test using the Interface)

To Learn more there is an excellent course on Using Configuration and Options in .NET Core by Steve Gordon in which he explains how to test IOptions

enter image description here

  • MOQ and xunit

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

1 Comment

Thank you! I've always wanted a clean way to use IOptions but not be bound to it - because it makes testing a pain in the ass.
1
  1. first add "appsettings.json" file in the root unitTestProject

  2. then use this code:

    private readonly Mock _fileRepMock;
    private IOptions _options;
    public FileServiceTest()
    {
       _fileRepMock = new Mock();
        var config = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
             .AddEnvironmentVariables()
             .Build();
        _options = Options.Create(config.GetSection("File").Get());
    }
  3. now you can use _options in the mock repository

    FileService fileService = new FileService(_fileRepMock.Object, _options);

2 Comments

That doesn't seem right. The whole point of mocking is not relying on the actual data.
1) Only Get<ClassName>() instead of Get(); 2) @basquiatraphaeu - mock data can be combined with a test-oriented appsettings.json, which would give more correct settings. I'd rather prefer and suggest having a test appsettings.json rather than mocking it.
1

Old topic but wanted to share this way with a static helper class

// Configure default options or specified ones
public static void Configure(this Mock<IOptions<MyOptions>> mock, MyOptions options = null)
    => mock.Setup(x => x.Value).Returns(options ?? new MyOptions());

// You can define your common configurations in this class
static readonly MyOptions Config2 = new()
    {
        FirstSetting = "value2"
    };

Test class

// Create tested class with options mock
Mock<IOptions<MyOptions>> options = new();

var myTestedClass = new MyTestClass(options.Object)

// Init or arrange test with default option object
options.Configure();

// Arrange test with a common option object 
options.Configure(Config2);

// Arrange test with specified option object
options.Configure(new MyOptions()
{
   FirstSetting = "value3"
});

As stated in the first comment : use new() and then Setup your mocked IOptions.Value

Comments

0

Agree with Aleha that using a testSettings.json configuration file is probably better.

And then, instead of injecting the IOption<SampleOptions>, you can simply inject the real SampleOptions in your class constructor, when unit test the class, you can do the following in a fixture or again just in the test class constructor:

var builder = new ConfigurationBuilder()
    .AddJsonFile("testSettings.json", true, true)
    .AddEnvironmentVariables();

var configurationRoot = builder.Build();
configurationRoot.GetSection("SampleRepo").Bind(_sampleRepo);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.