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
Async delegate assertions #464
Comments
What's the story for |
Async void tests were supposed to be removed from NUnit 3. Are the tests
|
OK, I understand now... I had referenced NUnit 3.0 alpha5 in my test project, but I was still using ReSharper's runner, which is 2.6.3, so it didn't reject the async void tests. Assert.Throws was failing, because it's in NUnit.Framework, not in the runner. If I use the 3.0 runner, I get the error on the async void tests as well. |
@StephenCleary Since Assert.Throws is capable of calling an async method, I'm not clear about why a separate ThrowsAsync is wanted. The only thing it seems to add is the ability for you to await the assert itself, but if you're calling an async method, you would generally await that inside the method, just as you do in the example. |
When Assert.Throws is used with an async method, it first sets up a context to execute that method. This context changes the behavior of await within the system under test. An Assert.ThrowsAsync would not establish a context and would not change the behavior of await. |
Can you post an example that shows Assert.Throws not working as you would wish the async version to work? Maybe a test that fails with Assert.Throws but would hypothetically pass with Assert.ThrowsAsync. |
Tried to write one today, but I couldn't get Assert.Throws to work with an async method at all; it always says "System.ArgumentException : 'async void' methods are not supported, please use 'async Task' instead". I mentioned this in my original comment: "Assert.Throws will try to create an AsyncInvocationRegion that will never succeed, since AIR (correctly) rejects async void methods and TestDelegate returns void." Using 3.0.0-beta-1 (3.0.5562.31848). |
OK, we'll see what we can do with this. |
It would make sense to be able to write a test like this: The easiest alternative I found was this, but I would much prefer to use async/await. 3.0.0-beta-3 (NuGet) |
The problem with That's why I prefer (note that |
Why is this marked as a feature? It is a bug because currently I am not able to use Assert.Throws with an async delegate, which worked before and there is no alternative now either? |
@dustinmorris it's a feature because it's a new signature that didn't exist before. If something worked before, it was something different from what is contemplated here. |
It's actually quite blocking, I have a lot of tests using The AggregateException / Wait is a workaround, but it removes the possibility to filter the exception, so it may include errors. If it's not considered as a bug (I get that), this is still a regression from version 2.0 in my opinion. |
I guess our original "support" was an unintended side-effect of supporting async void. Actually a good side effect in this case. :-) I upped the priority. |
This change in behavior from 2.0 is also blocking my upgrade to 3.0 as I also have a lot of tests using |
Using this: private Task SomethingAsync()
{
return Task.Factory.StartNew(
() => { throw new InvalidOperationException("Failed"); },
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default);
} Assert.That works: Assert.That(() => SomethingAsync(), Throws.TypeOf<InvalidOperationException>()); Assert.Throws doesn't: Assert.Throws<InvalidOperationException>(() => SomethingAsync());
|
Pretty nice workaround! Thanks! |
Yes, but still a workaround, not a fix... There should be a version of |
@StephenCleary I can't agree with you there: If we could figure out a way to make Throws work for both cases without conflict, I would favor that. |
The problem is that if On the other hand, I just realized that synchronously waiting for the task to complete is exactly what (in my PR,
As far as I can tell, it's not possible; if the new overload is named (interestingly, |
I have in many situations written unit tests with a SyncCtx. The AsyncContext in my AsyncEx library was originally written with unit tests in mind. Having code resume in the same thread is sometimes necessary. I commonly use SyncCtx in unit tests when testing ViewModels. I also have some code that assumes it will execute in a synchronized fashion (i.e., no more than one continuation at a time), which is the case if run in a UI or ASP.NET context, but (obviously) not in a thread pool context. On a side note, xUnit has made the interesting choice to always run their unit tests within a single-threaded SyncCtx. Previous (prerelease) versions of NUnit v3 would attempt to detect if a SyncCtx would be helpful (i.e., the async Throws support) and automatically inject one if necessary. I objected to this design because it can be confusing - part of the test would run without a SyncCtx and part with one. |
@thomaslevesque I'm afraid that ship has sailed. :-) NUnit's entire approach to async tests is "sync over async" - the test methods may be async but NUnit has to wait for them to complete in order to know whether they passed or failed. That blocks the thread on which the test is running, but other tests continue to run on other threads so it's usually not a problem, especially as async tests are usually not needed. In this case, we are one level lower dealing with Asserts but the same is true of them: NUnit has to know whether they failed or passed. |
I sort of agree. It's abstracted away enough from the unit test writer that they don't see the sync-over-async behavior. Right now NUnit blocks one thread per async unit test, but it's entirely conceivable that a future version may asynchronously wait for it to complete (more similar to how the ASP.NET framework works), and only have a single sync-over-async point for all async unit tests in a test run.
Yes, but this can be done in either a sync-over-async way, or an async-all-the-way way. The current PR is taking the async-all-the-way way, which I personally prefer but there are arguments to be made both ways. Both synchronous and asynchronous patterns are equally capable of propagating exceptions. |
Of course, but it can wait asynchronously for the delegate to complete... Anyway, you're the boss @CharliePoole, so I'll do as you say 😉 Does the name
True, but it's more awkward with the synchronous pattern, because we need to unwrap the |
@thomaslevesque I'll have to get back to this and do more than an email review - i.e. run the code. Also, I'd like to hear from other committers. @rprouse ? @simoneb ? To put it clearly, the implementation can be either synchronous or asynchronous. It has to be synchronous from the user point of view in order to be consistent with other choices in NUnit. |
Perhaps a bit too verbose but you could do something similar to the signature of Assert.That.
I'm a bit undecided on whether it is a good idea to return the task since that might give the wrong impression that you have to wait on it. |
@appel1, this would work, but then you'd have to specify the return type (
Well, you can't return the task anyway, since |
14 tests fail out of 27 after upgrade from 2.6.4 to 3.01, all of those 14 tests fail with this: System.ArgumentException : 'async void' methods are not supported, please use 'async Task' instead
at NUnit.Framework.Internal.AsyncInvocationRegion.Create(MethodInfo method)
at NUnit.Framework.Assert.Throws(IResolveConstraint expression, TestDelegate code, String message, Object[] args)
at NUnit.Framework.Assert.Throws(IResolveConstraint expression, TestDelegate code) Downgrading to 2.6.4. |
Do not downgrade just for this, you just have to change the signature of your test methods from This problem (which is an evolution) is not directly related to this issue. EDIT: ignore this message I was completely mistaken on the issue |
@PaulARoy, I think @dariusdamalakas is using @rprouse @CharliePoole, did you have time to review my PR? |
@thomaslevesque , correct, we are using @PaulARoy, if there's something wrong with the tests or a way to rewrite them, let me know. But it looks like this is breaking change between v2.6 and v3. |
My bad I was wrong on my message. Sorry for that. A solution that works:
Applied to your test:
It might not be perfect but it should work This is indeed a breaking change. |
@PaulARoy , hm, interesting. This does work, and this is the way i've ve used to write tests before figured out there is I'll leave my project pointing to Thanks. |
No problem! But indeed, |
[WIP] Fix #464: add support for Assert.ThrowsAsync
Any idea when this fix is available on NuGet? |
This will be in the 3.2 release which we hope to get out within the month. We are a bit behind schedule on the release because @CharliePoole and I have been busy with other projects recently. |
Fixed missing navigation data when adapter and test assemblies are in separate folders
All assertions that take TestDelegate should have correlating methods that take an asynchronous equivalent (Task AsyncTestDelegate()). E.g., Assert.Throws(TestDelegate) should have a matching Assert.ThrowsAsync(AsyncTestDelegate) or Assert.Throws(AsyncTestDelegate). This would enable this kind of usage:
Also, some synchronous assertion code has outdated code; e.g., Assert.Throws will try to create an AsyncInvocationRegion that will never succeed, since AIR (correctly) rejects async void methods and TestDelegate returns void. Ideally, AIR should only be used when executing the [Test] methods themselves, not within any other code.
The text was updated successfully, but these errors were encountered: