20

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?

alksdjg
  • 1,019
  • 2
  • 10
  • 26

7 Answers7

20

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; }
    } 
Hananiel
  • 421
  • 2
  • 9
18

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!

EricBDev
  • 1,279
  • 13
  • 21
2

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);
Alves RC
  • 1,778
  • 14
  • 26
2

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)

dani herrera
  • 48,760
  • 8
  • 117
  • 177
1

Might be a bit late, but here is a full implementation of a test options monitor you can use.

public class OptionsMonitorTestImplementation<TOptions> : IOptionsMonitor<TOptions>
{
    public OptionsMonitorTestImplementation(TOptions initialValue) => CurrentValue = initialValue;

    readonly List<Action<TOptions, string>> _listeners = new();
    public TOptions CurrentValue { get; private set; }
    public TOptions Get(string name) => throw new NotImplementedException();

    public IDisposable OnChange(Action<TOptions, string> listener)
    {
        _listeners.Add(listener);
        return new ActionDisposable(() => _listeners.Remove(listener));
    }

    public void UpdateOptions(TOptions options)
    {
        CurrentValue = options;
        _listeners.ForEach(listener => listener(options, string.Empty));
    }
    
    public sealed class ActionDisposable : IDisposable
    {
        readonly Action _action;

        public ActionDisposable(Action action) => _action = action;

        public void Dispose() => _action();
    } 
}
Mihail
  • 730
  • 4
  • 17
0

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/

Xavier John
  • 8,474
  • 3
  • 37
  • 51
-2

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

Marc Ziss
  • 659
  • 6
  • 10