-
Notifications
You must be signed in to change notification settings - Fork 34
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
Use Assert.ThatAsync #804
base: master
Are you sure you want to change the base?
Use Assert.ThatAsync #804
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# NUnit2055 | ||
|
||
## Use Assert.ThatAsync | ||
|
||
| Topic | Value | ||
| :-- | :-- | ||
| Id | NUnit2055 | ||
| Severity | Info | ||
| Enabled | True | ||
| Category | Assertion | ||
| Code | [UseAssertThatAsyncAnalyzer](https://github.com/nunit/nunit.analyzers/blob/master/src/nunit.analyzers/UseAssertThatAsync/UseAssertThatAsyncAnalyzer.cs) | ||
|
||
## Description | ||
|
||
You can use `Assert.ThatAsync` to assert asynchronously. | ||
|
||
## Motivation | ||
|
||
`Assert.That` runs synchronously, even if pass an asynchronous delegate. This "sync-over-async" pattern blocks | ||
the calling thread, preventing it from doing anything else in the meantime. | ||
|
||
`Assert.ThatAsync` allows for a proper async/await. This allows for a better utilization of threads while waiting for the | ||
asynchronous operation to finish. | ||
|
||
## How to fix violations | ||
|
||
Convert the asynchronous method call with a lambda expression and `await` the `Assert.ThatAsync` instead of the | ||
asynchronous method call. | ||
|
||
```csharp | ||
Assert.That(await DoAsync(), Is.EqualTo(expected)); // bad (sync-over-async) | ||
await Assert.ThatAsync(() => DoAsync(), Is.EqualTo(expected)); // good (proper async/await) | ||
``` | ||
|
||
<!-- start generated config severity --> | ||
## Configure severity | ||
|
||
### Via ruleset file | ||
|
||
Configure the severity per project, for more info see | ||
[MSDN](https://learn.microsoft.com/en-us/visualstudio/code-quality/using-rule-sets-to-group-code-analysis-rules?view=vs-2022). | ||
|
||
### Via .editorconfig file | ||
|
||
```ini | ||
# NUnit2055: Use Assert.ThatAsync | ||
dotnet_diagnostic.NUnit2055.severity = chosenSeverity | ||
``` | ||
|
||
where `chosenSeverity` can be one of `none`, `silent`, `suggestion`, `warning`, or `error`. | ||
|
||
### Via #pragma directive | ||
|
||
```csharp | ||
#pragma warning disable NUnit2055 // Use Assert.ThatAsync | ||
Code violating the rule here | ||
#pragma warning restore NUnit2055 // Use Assert.ThatAsync | ||
``` | ||
|
||
Or put this at the top of the file to disable all instances. | ||
|
||
```csharp | ||
#pragma warning disable NUnit2055 // Use Assert.ThatAsync | ||
``` | ||
|
||
### Via attribute `[SuppressMessage]` | ||
|
||
```csharp | ||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Assertion", | ||
"NUnit2055:Use Assert.ThatAsync", | ||
Justification = "Reason...")] | ||
``` | ||
<!-- end generated config severity --> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -110,6 +110,11 @@ public sealed class NUnitFrameworkConstantsTests | |
(nameof(NUnitFrameworkConstants.NameOfAssertIsNotNull), nameof(ClassicAssert.IsNotNull)), | ||
(nameof(NUnitFrameworkConstants.NameOfAssertNotNull), nameof(ClassicAssert.NotNull)), | ||
(nameof(NUnitFrameworkConstants.NameOfAssertThat), nameof(ClassicAssert.That)), | ||
#if NUNIT4 | ||
(nameof(NUnitFrameworkConstants.NameOfAssertThatAsync), nameof(ClassicAssert.ThatAsync)), | ||
#else | ||
(nameof(NUnitFrameworkConstants.NameOfAssertThatAsync), "ThatAsync"), | ||
#endif | ||
Comment on lines
+113
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know I used this pattern myself for |
||
(nameof(NUnitFrameworkConstants.NameOfAssertGreater), nameof(ClassicAssert.Greater)), | ||
(nameof(NUnitFrameworkConstants.NameOfAssertGreaterOrEqual), nameof(ClassicAssert.GreaterOrEqual)), | ||
(nameof(NUnitFrameworkConstants.NameOfAssertLess), nameof(ClassicAssert.Less)), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
#if NUNIT4 | ||
using Gu.Roslyn.Asserts; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using NUnit.Analyzers.Constants; | ||
using NUnit.Analyzers.UseAssertThatAsync; | ||
using NUnit.Framework; | ||
|
||
namespace NUnit.Analyzers.Tests.UseAssertThatAsync; | ||
|
||
[TestFixture] | ||
public sealed class UseAssertThatAsyncAnalyzerTests | ||
{ | ||
private static readonly DiagnosticAnalyzer analyzer = new UseAssertThatAsyncAnalyzer(); | ||
private static readonly ExpectedDiagnostic diagnostic = ExpectedDiagnostic.Create(AnalyzerIdentifiers.UseAssertThatAsync); | ||
private static readonly string[] configureAwaitValues = | ||
{ | ||
"", | ||
".ConfigureAwait(true)", | ||
".ConfigureAwait(false)", | ||
}; | ||
|
||
[Test] | ||
public void AnalyzeWhenIntResultIsUsed() | ||
{ | ||
var testCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" | ||
public void Test() | ||
{ | ||
Assert.That(GetIntAsync().Result, Is.EqualTo(42)); | ||
} | ||
|
||
private static Task<int> GetIntAsync() => Task.FromResult(42);"); | ||
RoslynAssert.Valid(analyzer, testCode); | ||
} | ||
|
||
[Test] | ||
public void AnalyzeWhenBoolResultIsUsed() | ||
{ | ||
var testCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" | ||
public void Test() | ||
{ | ||
Assert.That(GetBoolAsync().Result); | ||
} | ||
|
||
private static Task<bool> GetBoolAsync() => Task.FromResult(true);"); | ||
RoslynAssert.Valid(analyzer, testCode); | ||
} | ||
|
||
[Test] | ||
public void AnalyzeWhenAwaitIsNotUsedInLineForInt() | ||
{ | ||
var testCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" | ||
public async Task Test() | ||
{ | ||
var fourtyTwo = await GetIntAsync(); | ||
Assert.That(fourtyTwo, Is.EqualTo(42)); | ||
} | ||
|
||
private static Task<int> GetIntAsync() => Task.FromResult(42);"); | ||
RoslynAssert.Valid(analyzer, testCode); | ||
} | ||
|
||
// do not touch because there is no ThatAsync equivalent | ||
[Test] | ||
public void AnalyzeWhenExceptionMessageIsFuncString() | ||
{ | ||
var testCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" | ||
public async Task Test() | ||
{ | ||
Assert.That(await GetBoolAsync(), () => ""message""); | ||
} | ||
|
||
private static Task<bool> GetBoolAsync() => Task.FromResult(true);"); | ||
RoslynAssert.Valid(analyzer, testCode); | ||
} | ||
|
||
[Test] | ||
public void AnalyzeWhenAwaitIsNotUsedInLineForBool() | ||
{ | ||
var testCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" | ||
public async Task Test() | ||
{ | ||
var myBool = await GetBoolAsync(); | ||
Assert.That(myBool, Is.True); | ||
} | ||
|
||
private static Task<bool> GetBoolAsync() => Task.FromResult(true);"); | ||
RoslynAssert.Valid(analyzer, testCode); | ||
} | ||
|
||
[Test] | ||
public void AnalyzeWhenAwaitIsUsedInLineForInt([ValueSource(nameof(configureAwaitValues))] string configureAwait, [Values] bool hasMessage) | ||
{ | ||
var testCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" | ||
public async Task Test() | ||
{{ | ||
Assert.That(await GetIntAsync(){configureAwait}, Is.EqualTo(42){(hasMessage ? @", ""message""" : "")}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fully async. Yes it be written as |
||
}} | ||
|
||
private static Task<int> GetIntAsync() => Task.FromResult(42);"); | ||
RoslynAssert.Diagnostics(analyzer, diagnostic, testCode); | ||
} | ||
|
||
[Test] | ||
public void AnalyzeWhenAwaitIsUsedInLineForBool([ValueSource(nameof(configureAwaitValues))] string configureAwait, [Values] bool hasConstraint, [Values] bool hasMessage) | ||
{ | ||
var testCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" | ||
public async Task Test() | ||
{{ | ||
Assert.That(await GetBoolAsync(){configureAwait}{(hasConstraint ? ", Is.True" : "")}{(hasMessage ? @", ""message""" : "")}); | ||
}} | ||
|
||
private static Task<bool> GetBoolAsync() => Task.FromResult(true);"); | ||
RoslynAssert.Diagnostics(analyzer, diagnostic, testCode); | ||
} | ||
|
||
[Test] | ||
public void AnalyzeWhenAwaitIsUsedAsSecondArgument([ValueSource(nameof(configureAwaitValues))] string configureAwait, [Values] bool hasMessage) | ||
{ | ||
var testCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" | ||
public async Task Test() | ||
{{ | ||
↓Assert.That(expression: Is.EqualTo(42), actual: await GetIntAsync(){configureAwait}{(hasMessage ? @", message: ""message""" : "")}); | ||
}} | ||
|
||
private static Task<int> GetIntAsync() => Task.FromResult(42);"); | ||
RoslynAssert.Diagnostics(analyzer, diagnostic, testCode); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add practical examples that really need it like: var i = 0;
Task<int> GetResult() => Task.FromResult(i++);
Assert.That(GetResult, Is.GreaterThan(0).After(1000, 10)); The last line could also be: Similar one for throwing: |
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
#if NUNIT4 | ||
using Gu.Roslyn.Asserts; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using NUnit.Analyzers.Constants; | ||
using NUnit.Analyzers.UseAssertThatAsync; | ||
using NUnit.Framework; | ||
|
||
namespace NUnit.Analyzers.Tests.UseAssertThatAsync; | ||
|
||
[TestFixture] | ||
public sealed class UseAssertThatAsyncCodeFixTests | ||
{ | ||
private static readonly DiagnosticAnalyzer analyzer = new UseAssertThatAsyncAnalyzer(); | ||
private static readonly CodeFixProvider fix = new UseAssertThatAsyncCodeFix(); | ||
private static readonly ExpectedDiagnostic diagnostic = ExpectedDiagnostic.Create(AnalyzerIdentifiers.UseAssertThatAsync); | ||
private static readonly string[] configureAwaitValues = | ||
{ | ||
"", | ||
".ConfigureAwait(true)", | ||
".ConfigureAwait(false)", | ||
}; | ||
|
||
[Test] | ||
public void VerifyGetFixableDiagnosticIds() | ||
{ | ||
var fix = new UseAssertThatAsyncCodeFix(); | ||
var ids = fix.FixableDiagnosticIds; | ||
|
||
Assert.That(ids, Is.EquivalentTo(new[] { AnalyzerIdentifiers.UseAssertThatAsync })); | ||
} | ||
|
||
[Test] | ||
public void VerifyIntAndConstraint([ValueSource(nameof(configureAwaitValues))] string configureAwait, [Values] bool hasMessage) | ||
{ | ||
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@$" | ||
public async Task Test() | ||
{{ | ||
Assert.That(await GetIntAsync(){configureAwait}, Is.EqualTo(42){(hasMessage ? @", ""message""" : "")}); | ||
}} | ||
|
||
private static Task<int> GetIntAsync() => Task.FromResult(42);"); | ||
var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" | ||
public async Task Test() | ||
{{ | ||
await Assert.ThatAsync(() => GetIntAsync(), Is.EqualTo(42){(hasMessage ? @", ""message""" : "")}); | ||
}} | ||
|
||
private static Task<int> GetIntAsync() => Task.FromResult(42);"); | ||
RoslynAssert.CodeFix(analyzer, fix, diagnostic, code, fixedCode); | ||
} | ||
|
||
[Test] | ||
public void VerifyTaskIntReturningInstanceMethodAndConstraint([ValueSource(nameof(configureAwaitValues))] string configureAwait, [Values] bool hasMessage) | ||
{ | ||
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@$" | ||
public async Task Test() | ||
{{ | ||
Assert.That(await this.GetIntAsync(){configureAwait}, Is.EqualTo(42){(hasMessage ? @", ""message""" : "")}); | ||
}} | ||
|
||
private Task<int> GetIntAsync() => Task.FromResult(42);"); | ||
var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" | ||
public async Task Test() | ||
{{ | ||
await Assert.ThatAsync(() => this.GetIntAsync(), Is.EqualTo(42){(hasMessage ? @", ""message""" : "")}); | ||
}} | ||
|
||
private Task<int> GetIntAsync() => Task.FromResult(42);"); | ||
RoslynAssert.CodeFix(analyzer, fix, diagnostic, code, fixedCode); | ||
} | ||
|
||
[Test] | ||
public void VerifyBoolAndConstraint([ValueSource(nameof(configureAwaitValues))] string configureAwait, [Values] bool hasMessage) | ||
{ | ||
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@$" | ||
public async Task Test() | ||
{{ | ||
Assert.That(await GetBoolAsync(){configureAwait}, Is.EqualTo(true){(hasMessage ? @", ""message""" : "")}); | ||
}} | ||
|
||
private static Task<bool> GetBoolAsync() => Task.FromResult(true);"); | ||
var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" | ||
public async Task Test() | ||
{{ | ||
await Assert.ThatAsync(() => GetBoolAsync(), Is.EqualTo(true){(hasMessage ? @", ""message""" : "")}); | ||
}} | ||
|
||
private static Task<bool> GetBoolAsync() => Task.FromResult(true);"); | ||
RoslynAssert.CodeFix(analyzer, fix, diagnostic, code, fixedCode); | ||
} | ||
|
||
// Assert.That(bool) is supported, but there is no overload of Assert.ThatAsync that only takes a single bool. | ||
[Test] | ||
public void VerifyBoolOnly([ValueSource(nameof(configureAwaitValues))] string configureAwait, [Values] bool hasMessage) | ||
{ | ||
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@$" | ||
public async Task Test() | ||
{{ | ||
Assert.That(await GetBoolAsync(){configureAwait}{(hasMessage ? @", ""message""" : "")}); | ||
}} | ||
|
||
private static Task<bool> GetBoolAsync() => Task.FromResult(true);"); | ||
var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings($@" | ||
public async Task Test() | ||
{{ | ||
await Assert.ThatAsync(() => GetBoolAsync(), Is.True{(hasMessage ? @", ""message""" : "")}); | ||
}} | ||
|
||
private static Task<bool> GetBoolAsync() => Task.FromResult(true);"); | ||
RoslynAssert.CodeFix(analyzer, fix, diagnostic, code, fixedCode); | ||
} | ||
|
||
[Test] | ||
public void VerifyIntAsSecondArgumentAndConstraint([ValueSource(nameof(configureAwaitValues))] string configureAwait) | ||
{ | ||
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@$" | ||
public async Task Test() | ||
{{ | ||
↓Assert.That(expression: Is.EqualTo(42), actual: await GetIntAsync(){configureAwait}); | ||
}} | ||
|
||
private static Task<int> GetIntAsync() => Task.FromResult(42);"); | ||
var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" | ||
public async Task Test() | ||
{ | ||
await Assert.ThatAsync(() => GetIntAsync(), Is.EqualTo(42)); | ||
} | ||
|
||
private static Task<int> GetIntAsync() => Task.FromResult(42);"); | ||
RoslynAssert.CodeFix(analyzer, fix, diagnostic, code, fixedCode); | ||
} | ||
|
||
[Test] | ||
public void VerifyBoolAsSecondArgumentAndConstraint([ValueSource(nameof(configureAwaitValues))] string configureAwait) | ||
{ | ||
var code = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@$" | ||
public async Task Test() | ||
{{ | ||
↓Assert.That(message: ""message"", condition: await GetBoolAsync(){configureAwait}); | ||
}} | ||
|
||
private static Task<bool> GetBoolAsync() => Task.FromResult(true);"); | ||
var fixedCode = TestUtility.WrapMethodInClassNamespaceAndAddUsings(@" | ||
public async Task Test() | ||
{ | ||
await Assert.ThatAsync(() => GetBoolAsync(), Is.True, ""message""); | ||
} | ||
|
||
private static Task<bool> GetBoolAsync() => Task.FromResult(true);"); | ||
RoslynAssert.CodeFix(analyzer, fix, diagnostic, code, fixedCode); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add an example for detecting: public void Test()
{
Assert.That(() => Task.FromException<int>(new InvalidOperationException()), Throws.InvalidOperationException);
} With a code fix looking like: public async Task Test()
{
await Assert.ThatAsync(() => Task.FromException<int>(new InvalidOperationException()), Throws.InvalidOperationException);
} Especially notice the need to change the return type of the test from |
||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.