Davide Bellone
Davide Bellone

Reputation: 119

How to dynamically change IOptions<T> values in C# tests with Moq?

My C# classes are generally structured this way:

public class MyDummyService
{
    private readonly MyConfigClass _config;

    public MyDummyService(IOptions<MyConfigClass> options)
    {
        _config = options.Value;
    }

    public string DoSomethingWithTheNumber()
    {
        if (_config.SomeValue % 2 == 0)
            return "foo";
        return "bar";
    }

}
public class MyConfigClass
{
    public int SomeValue{get; set;}
    public string SomeName {get; set;}
}

Clearly, IOptions<MyConfigClass> is used to map configurations from the appSettings.json file.

Then, I can test such classes like this:

class MyDummyServiceTests
{
    protected AutoFixture.Fixture _fixture;
    protected MyDummyService _sut;
    protected MyConfigClass _simpleConfig;
    protected Mock<IOptions<MyConfigClass>> _mockConfig;

    public MyDummyServiceTests()
    {
        _fixture = new AutoFixture.Fixture();
        _mockConfig = new Mock<IOptions<MyConfigClass>>();

        _simpleConfig = _fixture.Build<MyConfigClass>()
          .With(i => i.SomeValue, 4)
          .With(i => i.SomeName, "Pippo")
          .Create();
    }

    [SetUp]
    public void Setup()
    {
        _mockConfig.SetupGet(m => m.Value).Returns(_simpleConfig);

        _sut = new MyDummyService(_mockConfig.Object);
    }

    [TearDown]
    public void TearDown()
    {
        _mockConfig.Reset();
    }

    public class DoSomethingWithTheNumber : MyDummyServiceTests
    {
        [Test]
        public void Should_ReturnFoo_WhenNumberIsEven()
        {
            _simpleConfig = _fixture.Build<MyConfigClass>()
              .With(_ => _.SomeValue, 10)
              .Create();

            var s = _sut.DoSomethingWithTheNumber();

            Assert.AreEqual("foo", s);
        }

        [Test]
        public void Should_ReturnBar_WhenNumberIsOdd()
        {
        
            _simpleConfig = _fixture.Build<MyConfigClass>()
              .With(_ => _.SomeValue, 69)
              .Create();

            var s = _sut.DoSomethingWithTheNumber();

            Assert.AreEqual("bar", s);
        }
    }
}

Yes, I know, I should use [TestCase] - you got the point

Now, when I set up tests for such classes, and I want to test a specific behavior that depends on the MyConfigClass.SomeValue value, I don't want to re-initialize everything. I can clearly just add _mockConfig.SetupGet(m => m.Value).Returns(_simpleConfig); or _sut = new MyDummyService( Options.Create(_simpleConfig)); but I want to keep my tests as small as possible, and only set the configuration value I need.

If I run the tests as such, DoSomethingWithTheNumber() does not see MyConfigClass.SomeValue with the correct value, because it is initialized and assigned to _config during StartUp phase, so before I set the correct value in my tests.

How can I improve my approach and set only the values necessary to have the specific test pass?

Note: I do not expect my configurations to change at runtime. Therefore, I can use IOptions<T>, IOptionsMonitor<T>, or IOptionsSnapshot<T> - even if they are different meanings, as explained here.

Upvotes: 2

Views: 369

Answers (1)

Evgenii Koroliuk
Evgenii Koroliuk

Reputation: 132

You just need to reinitialize _simpleConfig but do not change the reference to:

    [Test]
    public void Should_ReturnFoo_WhenNumberIsEven()
    {
        _simpleConfig.SomeValue = 10;

        var s = _sut.DoSomethingWithTheNumber();

        Assert.AreEqual("foo", s);
    }

Upvotes: 0

Related Questions