'How can I build an IOptionsMonitor<T> for testing?

For the normal IOptions interface, you can manually build an instance e.g. this SO question.

Is there any equivalent way to make an IOptionsMonitor instance without using DI?



Solution 1:[1]

You can do something like below and and then use that for testing:

    public class TestOptionsMonitor : IOptionsMonitor<MyOptions>
    {
        public TestOptionsMonitor(MyOptions currentValue)
        {
            CurrentValue = currentValue;
        }

        public MyOptions Get(string name)
        {
            return CurrentValue;
        }

        public IDisposable OnChange(Action<MyOptions, string> listener)
        {
            throw new NotImplementedException();
        }

        public MyOptions CurrentValue { get; }
    } 

Solution 2:[2]

nice answer from Hananiel

Here the generic version of it:

public class TestOptionsMonitor<T> : IOptionsMonitor<T>
    where T : class, new()
{
    public TestOptionsMonitor(T currentValue)
    {
        CurrentValue = currentValue;
    }

    public T Get(string name)
    {
        return CurrentValue;
    }

    public IDisposable OnChange(Action<T, string> listener)
    {
        throw new NotImplementedException();
    }

    public T CurrentValue { get; }
}

and simply create an instance with your object!

Solution 3:[3]

Disclaimer: If you're avoiding external libraries the previous answers will do the trick! Consider this one if you'd like to leverage an existing library to Mock stuff for you!

If you're using the NSubstitute library for Mocking you can mock out an IOptionsMonitor by doing the following:

// Arrange
var mockedOptions = Substitute.For<IOptionsMonitor<BasicCredentialSchemaOptions>>();
mockedOptions.CurrentValue.Returns(new BasicCredentialSchemaOptions(/*snip*/));

// Act
var result = mockOptions.CurrentValue;    // Does not throw and fires off NSubstitue's Returns

// Assert
Assert.NotNull(result);

Solution 4:[4]

None of the samples above worked for me because the code calls OnChange and it throws NotImplementedException.

Here is the code I ended up using.

public class TestOptionsMonitor<TOptions> : IOptionsMonitor<TOptions>
{
    private Action<TOptions, string>? _listener;

    public TestOptionsMonitor(TOptions currentValue) => CurrentValue = currentValue;

    public TOptions CurrentValue { get; private set; }

    public TOptions Get(string name) => CurrentValue;

    public void Set(TOptions value)
    {
        CurrentValue = value;
        _listener?.Invoke(value, string.Empty);
    }

    public IDisposable OnChange(Action<TOptions, string> listener)
    {
        _listener = listener;
        return Mock.Of<IDisposable>();
    }
}

Which is based on this blog. https://benfoster.io/blog/20200610-testing-ioptionsmonitor/

Solution 5:[5]

Because iOptionsMonitor is not the subject of your test, you can replace it by a mock that simulate the behaviour of the real object.

Using moq:

var iOptionsMonitor =
     Mock
     .Of<IOptionsMonitor<MyOptions>>(
        x => 
           x.CurrentValue.YourOption1 == "some value" &&
           x.CurrentValue.YourOption2 == 21 &&
           x.CurrentValue.YourOption3 == true
     );

Remember to install Moq package:

dotnet add package Moq 

Be free to setup your mock for your custom needs. For example, to set behaviour for other interface methods (Get, OnChange)

Solution 6:[6]

I was going crazy trying to mock the object, this was much easier. Just new up a TestOptionsMonitor

var options = new TestOptionsMonitor(new MyOptions{ Option1 = "Test" }); 

using the above TestOptionsMonitor and you'll be good to go

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Hananiel
Solution 2 EricBDev
Solution 3 Alves RC
Solution 4 Xavier John
Solution 5 dani herrera
Solution 6 Marc Ziss