Testing with bUnit and Moq a razor component that uses Fluxor
Asked Answered
T

2

5

I used Fluxor for state management in in one of the razor components in my Blazor app and I'm not really sure how to set it up for testing with Moq. I can't just assing a value to students because it's an init only property. Is there a specific way to set up Fluxor for testing?

code

var teacherService = new Mock<ITeacherService>();
            var  localStorage = new Mock<ILocalStorageService>();
            var  studentService = new Mock<IStudentService>();
            var  toastService = new Mock<IToastService>();
            var  NavigationManager = new Mock<NavigationManager>();
            var  Dispatcher = new Mock<IDispatcher>();
            var actionSubscriber = new Mock<IActionSubscriber>();

            var StudentsState = new Mock<IState<StudentsState>>();
            var TeacherState = new Mock<IState<TeacherState>>();

            // Help here
            StudentsState.Setup(t => t.Value.Students = )
Towhee answered 22/5, 2021 at 5:14 Comment(2)
This is a MOQ question, not really a bUnit question. Anyway, take a look at github.com/Moq/moq4/wiki/Quickstart#properties. It explains how to define return values for properties, e.g. mock.Setup(foo => foo.Name).Returns("bar");Fundament
Here are some examples of how to write tests for rendering of components, reducers and dispatching of actions with Blazor and Fluxor: github.com/mrpmorris/Fluxor/issues/397#issuecomment-1517884399Antebellum
K
5

I was stuck on this for quite a while, but went looking through Fluxor's glitter community and found something that might help! I understand that you'd like to test using Moq, but perhaps what I found might be easier/suit your purposes for testing Fluxor components!

Instead of using Moq, I followed this format and managed to get something working:

Test.cs

public class InhousePetOwnerStateTests
{
    private readonly IServiceProvider ServiceProvider;
    private readonly IStore Store;
    private readonly IState<InhousePetOwnerListState> State;
    public InhousePetOwnerStateTests()
    {
        var services = new ServiceCollection();
        services.AddFluxor(x =>
        x
            .ScanAssemblies(GetType().Assembly)
            .ScanTypes(typeof(InhousePetOwnerListState), typeof(Reducers)));
    
        ServiceProvider = services.BuildServiceProvider();
        Store = ServiceProvider.GetRequiredService<IStore>();
        State = ServiceProvider.GetRequiredService<IState<InhousePetOwnerListState>>();
        Store.InitializeAsync().Wait();
    }

    [Fact]
    public void SetInhousePetOwnerList_Empty_StateUpdated()
    {
        Store.Dispatch(new SetInhousePetOwnerListAction(new()));
        Assert.Empty(State.Value.InhousePetOwners);
    }
        
    [Fact]
    public void SetInhousePetOwnerList_MultipleInhouseOwners_StateUpdated()
    {
        List<InhousePetOwner> inhousePetOwners = new List<InhousePetOwner>
        {
            new InhousePetOwner()
            {
                InhousePetOwnerId = 1,
                PetOwnerId = 1,
                TimeIn = System.DateTime.Now
            },
            new InhousePetOwner()
            {
                InhousePetOwnerId = 2,
                PetOwnerId = 2,
                TimeIn = System.DateTime.Now
            },
        };
        Store.Dispatch(new SetInhousePetOwnerListAction(inhousePetOwners));
        Assert.Equal(2, State.Value.InhousePetOwners.Count);
    }
}

In the constructor, we create a service collection and add Fluxor (just like you would to your Startup.cs), then scan for assemblies.

In my code above, I scanned for the Reducers and the State. Please refer to my directory structure here (you'll get an idea of how I decided what to include).

Actions.cs

public class SetInhousePetOwnerListAction
{
    public List<InhousePetOwner> InhousePetOwners { get; }
    public SetInhousePetOwnerListAction(List<InhousePetOwner> inhousePetOwners)
    {
        InhousePetOwners = inhousePetOwners;
    }
}

Reducers.cs (scanned in the constructor)

public static class Reducers
{
    [ReducerMethod]
    public static InhousePetOwnerListState ReduceSetInhousePetOwnerListAction(InhousePetOwnerListState state, SetInhousePetOwnerListAction action)
    {
        return new InhousePetOwnerListState(inhousePetOwners: action.InhousePetOwners);
    }
}

InhousePetOwnerState.cs (scanned in the constructor)

public class InhousePetOwnerListState
{
    public List<InhousePetOwner> InhousePetOwners { get; }

    private InhousePetOwnerListState() { }

    public InhousePetOwnerListState(List<InhousePetOwner> inhousePetOwners)
    {
        InhousePetOwners = inhousePetOwners;
    }
}

What this solution does is it allows you to inject all your Fluxor states, actions and reducers, enabling you to call them as you normally would - I've found it pretty easy to test this way!

Knitwear answered 6/12, 2021 at 14:30 Comment(3)
From your Reducers.cs cs file, I can't see a definition of Reducers type which you have used to scan in the constructor of InhousePetOwnerStateTests. I'm talking about this line .ScanTypes(typeof(InhousePetOwnerListState), **typeof(Reducers)**));Eh
@RobertMrobo Thanks for the comment! I've updated the file above to reflect the definition of Reducers.cs; hopefully, it's clearer now!Knitwear
Just a note for anyone using Effects that rely on services you wrote yourself: You'll need to register mock services with the test context so the IoC can create them. If you don't, the IoC will attempt to initialize fluxor and fail because it can't find the custom services.Alkalize
F
1

The way I did this was to use the set up to return a whole state record, configured the way that I wanted it.

So if you have a IState where StudentsState is readonly

You can do:

        var studentState = new Mock<IState<StudentsState>>();
        studentState.Setup(s => s.Value)
            .Returns(
                new StudentsState
                {
                    Students = new List<StudentRecord>
                    {
                        new StudentRecord(),
                        new StudentRecord(),
                    }
                });

        var TestContext = new Bunit.TestContext();
        TestContext.Services.AddScoped(_ => studentState.Object);

        var cut = TestContext.RenderComponent<StudentListComponent>();

        var studentRows = cut.FindAll(".student-row");

        Assert.AreEqual(2, studentRows.Count, "Should have one row for each student in the state");

If you need more complext interaction with the state you need to actually set up Fluxor to run on the TestContext something like:

    var TestContext = new Bunit.TestContext();
    TestContext.Services.AddFluxor(options =>
    {
        options.ScanAssemblies(Assembly.GetExecutingAssembly(), typeof(StudentsState).Assembly);
    });

    var store = TestContext.Services.GetService<IStore>();
    await store.InitializeAsync();

    IDispatcher dispatcher = TestContext.Services.GetService<IDispatcher>();
    dispatcher.Dispatch(new SetUpStudentsStateAction());

(Note that if you set up the Fluxor like this then you'll need to mock up your data source so that fluxor can load up the data in the Effect)

Finagle answered 21/10, 2021 at 12:56 Comment(2)
Could you flesh this out please- i.e. put in some context about your setup/constructor. I'm interested in how your mock Istate is injected into your componentUnspent
@Unspent I added some more context for the first example for mocking up the IState - here you just pass it into the services so that the component can load it. If you need Fluxor to actually run and handle actions then you can add fluxor to the test context and fire off actions to get the state in the right condition for your tests - you just have to make sure you call InitializeAsync before trying to use itFinagle

© 2022 - 2025 — McMap. All rights reserved.