mrpmorris / Fluxor

Fluxor is a zero boilerplate Flux/Redux library for Microsoft .NET and Blazor.
MIT License
1.22k stars 139 forks source link

Simple examples for testing #397

Closed eharbitz closed 1 year ago

eharbitz commented 1 year ago

It would be great to have some simple examples in the documentation that demonstrates how to make simple tests:

For example:

Thank you!

mrpmorris commented 1 year ago

Fluxor is UI agnostic. To test Blazor components, you can use BUnit.

eharbitz commented 1 year ago

Ok, I see.

It is just a bit hard to figure out how to set everything up with services and initializing the Fluxor store in the test framework. Would be great with an example on how to test the tutorial examples (https://github.com/mrpmorris/Fluxor/tree/master/Source/Tutorials/02-Blazor/02A-StateActionsReducersTutorial) with Bunit.

This seems to be a common problem given that there are already at least four other closed issues on this: https://github.com/mrpmorris/Fluxor/issues/368, https://github.com/mrpmorris/Fluxor/issues/187, https://github.com/mrpmorris/Fluxor/issues/211, https://github.com/mrpmorris/Fluxor/issues/15.

eharbitz commented 1 year ago

Just wanted to follow up on my own question here in case somebody else is looking for the same.

What we wanted to test was the business logic in the reducers. (I read somewhere that business logic should not go in the reducers, but I can't see where it would be better to put it).

Let's say you have the following state, action and reducer

public record CounterState
{
    public int Count { get; init; }
}

public record IncreaseCounterAction(int Increment);

[ReducerMethod]
public static CounterState OnIncreaseCounter(CounterState state, IncreaseCounterAction action)
{
    return state with { Count = state.Count + action.Increment };
}

There is no need to mock a lot of services and set up the Fluxor store to test this. As everything defined here is pure C# public records and methods, a simple test would look like this:

[Fact]
public void CounterTest()
{
    var initialState = new CounterState { Count = 0 };

    var updatedState = OnIncreaseCounter(initialState, new IncreaseCounterAction(Increment: 5));

    Assert.Equal(5, updatedState.Count);
}

If you want to make a render test a of a Blazor component with BUnit, you can mock the Fluxor state like this

[Fact]
public void RenderTest()
{
    using var testContext = new TestContext();
    Mock<IState<CounterState>> mockCounterState = new();

    var initialState = new CounterState() { Count = 0 };
    mockCounterState.Setup(x => x.Value).Returns(initialState);

    testContext.Services.AddScoped(provider => mockCounterState.Object);

    var cut = testContext.RenderComponent<CounterComponent>();

    Assert.Contains("Counter state: 5", cut.Markup);
}

If you use IStateSelection in your component, the test could look something like this

[Fact]
public void RenderTest()
{
    using var testContext = new TestContext();
    Mock<IStateSelection<CounterState, SomeSubState>> mockSubState = new();

    var initialState = new SomeSubState() { SomeProperty = 57 };
    mockSubState.Setup(x => x.Value).Returns(initialState);

    testContext.Services.AddScoped(provider => mockSubState.Object);

    var cut = testContext.RenderComponent<CounterComponent>();

    Assert.Contains("The property in the substate is 57", cut.Markup);
}

If you want to test what actions are dispatched you could do something like this

[Fact]
public void DispatchTest()
{
    using var testContext = new TestContext();
    Mock<IState<CounterState>> mockCounterState = new();
    Mock<IDispatcher> mockDispatcher = new();

    var initialState = new CounterState() { Count = 0 };
    mockCounterState.Setup(x => x.Value).Returns(initialState);

    testContext.Services.AddScoped(provider => mockCounterState.Object);
    testContext.Services.AddScoped(provider => mockDispatcher.Object);

    var cut = testContext.RenderComponent<CounterComponent>();

    var counterInput = cut.Find("#counter-input");

    counterInput.Change("12"); // Dispatches the IncreaseCounterAction

    mockDispatcher.Verify(
        d => d.Dispatch(It.Is<IncreaseCounterAction>(h => h == new IncreaseCounterAction(12))),
        Times.Once
    );
}
mrpmorris commented 1 year ago

Thanks for sharing!