bUnit-dev / bUnit

bUnit is a testing library for Blazor components that make tests look, feel, and runs like regular unit tests. bUnit makes it easy to render and control a component under test’s life-cycle, pass parameter and inject services into it, trigger event handlers, and verify the rendered markup from the component using a built-in semantic HTML comparer.
https://bunit.dev
MIT License
1.14k stars 105 forks source link

Issues with Fluxor in version 1.24.10 #1266

Closed marcoatribeiro closed 11 months ago

marcoatribeiro commented 11 months ago

I use Fluxor for state management and one of the components inherits from FluxorComponent (as descibed here) in order to make it aware of any state changes.

To test this component (and others that handle state, I create the following helper extension method, which is invoked in the test class constructor:

public static Mock<IDispatcher> AddFluxorServices(this TestContext ctx)
{
    var dispatcherMock = new Mock<IDispatcher>();
    ctx.Services.AddScoped(_ => dispatcherMock.Object);
    ctx.Services.AddScoped(_ => Mock.Of<IActionSubscriber>());
    return dispatcherMock;
}

Until version 1.23.9 and for all other components that handle state (but do not inherit from FluxorComponent this works fine. However, for this specific component in this last version, the error described here started to show up when executing the tests (but not when the actual application runs).

After some digging (including the latest changes in bUnit's TestContextBase) I managed to create a workaround by overriding the Disposable method in the culprit test class:

protected override void Dispose(bool disposing)
{
    _ = Services.DisposeAsync();
    Services.Dispose();
    base.Dispose(disposing);
}

I understand it is hard to determine the root cause of the issue, as it involves interaction between two different libraries, but I hope that, at least, this tickjet help others which might run into similar issues.

Version info:

linkdotnet commented 11 months ago

Hey @marcoatribeiro,

thanks for reporting the issue. As you noticed we changed the ordering of disposables in our service container in 1.24.

I tried to find the root cause - but I am a bit puzzled. While FluxorComponent itself, has an IActionSubscriber, that in production code would resolve into ActionSubscriber that indeed could throw that exception, you mocked this completely out. Do you have a minimal repro for us?

Slightly related, I really disagree with the implementation Fluxor did:

~DisposableCallback()
{
    if (!Disposed && WasCreated)
    {
        string message = $"{nameof(DisposableCallback)} with Id \"{GetIdInfo()}\" was not disposed";
        throw new InvalidOperationException(
            $"{message}. See https://github.com/mrpmorris/Fluxor/tree/master/Docs/disposable-callback-not-disposed.md" +
            $" for more details");
    }
}

IMHO, it is a misuse of finalizers. They shouldn't be used for some sanity checks. Especially because you can't catch the exception at runtime, possibly crashing the whole application.

egil commented 11 months ago

@marcoatribeiro can you create a minimal reproducable example we can download and play with to understand the problem better.

marcoatribeiro commented 11 months ago

Hi @egil

I tried to create a sample project which triggers the error, but it is quite challenging as the behavior is non-deterministic:

If I run into this issue again in the future and I'm able to create a reproducable example, I'll let you know. For now, I'm happy with the workaround I mentioned and, thus, I'm closing this issue. Thanks a lot for you disposition to help.

egil commented 11 months ago

Try the latest preview release. @linkdotnet did change our renderer such that it always disposes of components before the renderer.