bUnit-dev/bUnit

Issues with Fluxor in version 1.24.10

marcoatribeiro opened this issue · 4 comments

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:

  • bUnit version: 1.24.10
  • .NET Runtime and Blazor version: 7.0
  • OS type and version: Windows 11
  • Fluxor: 5.9.1

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

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

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 just run the culprit test class (the one where I have overriden the Dispose method) it works fine.
  • If I run other tests for other classes which also handle State using Fluxor (but do not inherit from FluxorComponent) they also end OK
  • However, if I run 2, 3 or more tests at once, they fail, and, even so, it is not 100% of the time

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

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