I'm hitting a bit of a chicken and egg problem setting up my mocks for Reactive Extensions code in a constructor. Here's the class under test (along with the service interface it depends on):
class MyViewModel
{
public int Thing { get; set; }
public MyViewModel(IMyService service)
{
service.StreamOfThings.Subscribe(x => Thing = x));
}
public void SomeClickEvent()
{
// Do something with `Thing`
}
}
public interface IMyService
{
IObservable<int> StreamOfThings { get; }
}
To make testing easier, I have also defined a custom attribute named AutoMockData
I can use to use Moq to inject mock instances into my classes through AutoFixture:
public class AutoMockDataAttribute : AutoDataAttribute
{
private static IFixture Create() =>
new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
public AutoMockDataAttribute() : base(Create) {}
}
With this, I am ready to write my test (using NUnit3):
[Test, AutoMockData]
public void Verify_some_behavior(
[Frozen] Mock<IMyService> mockService,
MyViewModel vm)
{
mockService.Setup(x => x.StreamOfThings).Returns(Observable.Return(100));
vm.SomeClickEvent();
vm.Thing.Should().Be(100);
}
This test fails. I believe this fails because the constructor of MyViewModel
sets up an observable pipeline on a different instance of IObservable
than the one I set up in the unit test method.
The ideal solution here would be to use the IObservable<>
instance that was set up using AutoFixture, but I'm not sure how to best do that. It would somehow need to already set up a pre-constructed pipeline for me.
The workaround I have found is to not use the AutoMock
functionality of AutoFixture
and instead construct a Fixture
directly and directly use the .Freeze()
and .Create()
methods in the proper order. However, this leads to an arguably more difficult to read and less clean unit test body.
How can I continue to implement my test as shown here but also be able to set up any observables before they are used in the SUT's constructor?
I think the issue here is that you never actually exercise the observable, so Thing
keeps the value that AutoFixture assigned it on creation.
In the example below the Subject
is frozen as an IObservable<int>
which is then resolved as the return value for the StreamOfThings
. Then the subject is forced to trigger the subscribers.
[Test, AutoMockData]
public void Verify_some_behavior(
[Frozen(Matching.ImplementedInterfaces)] Subject<int> observable,
MyViewModel vm)
{
observable.OnNext(100);
vm.SomeClickEvent();
vm.Thing.Should().Be(100);
}
The example is equivalent to the following:
[Test, AutoMockData]
public void Verify_some_behavior(
Subject<int> observable,
[Frozen] Mock<IMyService> mockService,
MyViewModel vm)
{
mockService.Setup(x => x.StreamOfThings).Returns(observable);
observable.OnNext(100);
vm.SomeClickEvent();
vm.Thing.Should().Be(100);
}
This way of writing the test should also make it obvious how to handle multiple observable properties.