-
-
Notifications
You must be signed in to change notification settings - Fork 109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Investigate running test code and renderer code in the same thread/sync context #124
Comments
Inspiration from @jspuij: I've written my own for BlazorWebView, because I have a dedicated thread running the Blazor Renderer. This is more like the classic desktop "UI" model, where you have a single thread running the UI. The thread hopping on the thread pool that the server side renderer exhibits might confuse desktop users. https://github.com/jspuij/BlazorWebView/blob/master/src/BlazorWebView/PlatformDispatcher.cs |
Your safest bet would be to use a variant of my dispatcher. Then run the tests on the threadpool (most test runners probably will, but dedicated threads will probably as well). Create a SynchronizationContext for async operations or use this one: https://github.com/StephenCleary/AsyncEx/wiki/AsyncContext. |
@jspuij xUnit runs tests on the thread pool and has, as far as I know, it's own async context. I am pretty sure the other test frameworks does something similar. Could a strategy be to adopt the test frameworks async context/dispatcher? I.e. simply pass the already active async context when the test runs to the Renderer. |
Yes, that will work. You can probably check SynchronizationContext.Current to see if there is one. If there is one (xunit) good, if there isn't one, create your own. Some test frameworks won't provide them, some will only provide them if the test is async (e.g. returns task). |
I will argue that this is impossible. I arrived at this issue because I ran into another A void-returning async method is in principle not awaitable. Under the assumption that you cannot modify the void-returning async method, there's no operation you can do to ensure that all its asynchronous continuations are finished: see e.g. this stackoverflow question. I realize that you're already talking about creating a custom Also in #177 I asked the question "Why do we need the I got the answer that returning void is simpler for the users, which I found a bit unsatisfactory because to me tasks are simpler. But now I understand the real answer: it's impossible to return tasks because blazor doesn't return the tasks. Finally I remark that this argument is imho worth mentioning to explain why you need those methods e.g. here. |
@JeroenBos thanks for the input. My gut feeling is that you are correct. In some places Blazor does return a Task, in particular when we trigger event handlers, so in some cases it might work. Can you describe the scenario you mention in more details? |
Well my particular scenario might not be so interesting because I'm shooting myself in the foot by using MSTest. public class MyTests // : RazorTestBase
{
[TestMethod]
public async Task Run() // similar to SnapshotTest.Run
{
Services.AddDefaultTestContextServices();
var (cutId, cut) = this.Renderer.RenderComponent<MyComponent>(parameters);
var icut = cut.ToIRenderedComponent(cutId, this.Services);
using var waiter = new WaitForStateHelper(icut, somePredicate);
// some assertion
}
}
/// MyComponent.razor:
@code
{
public override async Task SetParametersAsync(ParameterView parameters)
{
await Task.Delay(100);
}
} But this doesn't work and can never work without the cooperation of the
Without any task to await on the main thread you'd have to resort to hacks to keep the process alive. My first reaction was that I should be able to await |
Thanks @JeroenBos. Which version of MSTest are you using, and does it allow you to specify a custom sync context? I ask because if not, then I probably need to stop telling folks that bUnit and MSTest works together. Btw., im curious, why are you using WaitForStateHelper directly instead of the WaitFor... extension methods? |
I reference MSTest like so: MSTest doesn't provide a way to set a sync context, nor do I know of a sufficient sync context out of the box. My solution looks something like this using the AsyncEx library (also mentioned above by jspuij). Using that context everything works like a charm. As for the WaitForStateHelper. I don't have a good reason, by accidental fluke. I never changed it, but I will now! It's simply because I discovered the helper before the extension methods. Not because the documentation isn't good, but because I'm limited by linux/vscode and Omnisharp's intellisense just isn't good enough to do library exploration so I manually browsed the source code. Another remark about whether MSTest and bUnit are compatible: Blazor has a |
OK. It would be super helpful to other would-be MSTest+bUnit folks if you could create small hello world sample test project where you show this. No fancy tests, just the bare minimum. Then I can update the docs or perhaps create a template for those that need to use MSTest. |
Moving this to the backlog. It might be possible, but currently it doesn't seem like it. |
This is true and by design, otherwise Blazor would never be able to render anything untill the last task is awaited, so it runs through a synchronous path, while firing off async tasks that will complete in the future (and might trigger another render) and are never awaited. The easiest way to make sure that the test finished is to create a TaskCompletionSource on the main thread that you await, and trigger the completion of the TCS inside the async Blazor Lifecycle methods. |
That would probably be a workaround for making But if we are in a scenario where a components life cycle methods awaits an uncompleted task during the initial render, e.g. a timer task, the second render will happen later after the await of Unless I am missing something, putting things in the same sync context wont really make a difference to the APIs then. |
You are right. I'm not familiar with bUnit (sorry, shame on me), so I understand that you've got WaitFor methods that wait for elements to occur. I was just suggesting for @JeroenBos to use a TCS inside his own test, but he can use WaitFor just as well. It's inherent to the Blazor architecture and no SynchronizationContext will change this. |
OK, in that case I feel confident enough to close this issue. If somebody figures out a good way to do this that improves the usability of bUnit, we can revisit this. |
The library has some rather complex locking checks in place to avoid memory coherence problems, that can cause tests to fail when the test code and renderer code is not running on the same core/cpu.
It might be worth switching to a model, where both test code and renderer share the same dispatcher/sync context. So let's investigate. Here are a few questions to answer:
Related issue: #118
The text was updated successfully, but these errors were encountered: