288

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);
        }
    }
}
Pang
  • 9,564
  • 146
  • 81
  • 122
Matt
  • 2,891
  • 2
  • 13
  • 4
  • Are you confusing the meaning of mocking? You mock on an interface and configure it to return a specified value. For `IOptions`you only have to mock `Value` to return the class you desire – Tseng Nov 29 '16 at 22:00

11 Answers11

533

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.

Necoras
  • 6,743
  • 3
  • 24
  • 45
96

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

patvin80
  • 961
  • 5
  • 3
41

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);
    }
}
aleha_84
  • 8,309
  • 2
  • 38
  • 46
  • 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. – Harry Jan 02 '18 at 10:29
  • 6
    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" extension method available. – Kinetic Apr 21 '18 at 22:49
  • I had to run dotnet add package Microsoft.Extensions.Configuration.Json to get this to work. Great answer! – Leonardo Wildt Nov 26 '18 at 19:13
  • 1
    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". – bpz Jan 14 '19 at 11:28
  • Using MSTest unit testing? – Kiquenet Oct 20 '22 at 15:38
28

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; }
}
matei
  • 436
  • 5
  • 4
18

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"});
Sujoy
  • 1,051
  • 13
  • 26
  • This answer is a good addendum to [Necoras' highly, up-voted answer](https://stackoverflow.com/a/40898882/2615878) since it adds the curly brace object initialization. – Mike Grove aka Theophilus Nov 21 '22 at 15:34
16

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");
    }
}
Frank Rem
  • 3,632
  • 2
  • 25
  • 37
  • 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). – Erik Philips May 10 '19 at 02:19
  • 7
    @ErikPhilips My code shows how to mock IOptions as requested by the OP. I agree that it does not test anything useful in itself, but it can be useful testing something else. – Frank Rem May 11 '19 at 13:24
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);
Robert Corvus
  • 2,054
  • 23
  • 30
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.

Mithrandir
  • 131
  • 7
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

Hemendr
  • 673
  • 6
  • 12
  • 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. – codewise Jul 21 '23 at 23:25
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);
  • That doesn't seem right. The whole point of mocking is not relying on the actual data. – basquiatraphaeu Jun 15 '23 at 13:11
  • 1) Only Get() 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. – Ivan Silkin Aug 02 '23 at 11:19
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);
Pang
  • 9,564
  • 146
  • 81
  • 122