Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-process.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 9.x
dotnet-version: 10.x
- name: Restore dependencies
run: dotnet restore $SOLUTION_LOCATION
- name: Build
Expand Down
15 changes: 10 additions & 5 deletions docs/articles/nunit/writing-tests/attributes/retry.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

RetryAttribute is used on a test method to specify that it should be rerun if it fails, up to a maximum number of times.

[!code-csharp[Retry](~/snippets/Snippets.NUnit/Attributes/RetryAttributeExamples.cs#Retry)]

Notes:

1. The argument you specify is the total number of attempts and __not__ the number of retries after an initial failure.
So `[Retry(1)]` does nothing and should not be used.
2. It is not currently possible to use `RetryAttribute` on a `TestFixture` or any other type of test suite. Only single
tests may be repeated.
3. If a test has an unexpected exception, an error result is returned and it is not retried. Only assertion failures can
trigger a retry. To convert an unexpected exception into an assertion failure, see the
[ThrowsConstraint](xref:throwsconstraint).
2. It is not currently possible to use `RetryAttribute` on a `TestFixture` or any other type of test suite.
Only single tests may be repeated.
3. If a test has an unexpected exception, an error result is returned and it is not retried.

From NUnit 4.5.0 you can enable retry on an expected exception such as `TimeoutException`
by setting the `RetryExceptions` property. The value of this property is an array of anticipated exceptions that should be retried.

[!code-csharp[RetryWithRetryExceptions](~/snippets/Snippets.NUnit/Attributes/RetryAttributeExamples.cs#RetryWithRetryExceptions)]
69 changes: 69 additions & 0 deletions docs/snippets/Snippets.NUnit/Attributes/RetryAttributeExamples.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Diagnostics;
using System.Threading.Tasks;
using NUnit.Framework;

namespace Snippets.NUnit;

public class RetryAttributeExamples
{
#region Retry
[TestFixture]
public sealed class RetryTests
{
private readonly Random _random = new(42);

[Test]
[Retry(5)]
public async Task OperationShouldPassIn1s()
{
var sw = Stopwatch.StartNew();
string result = await ExpensiveOperation();
sw.Stop();
Assert.That(sw.ElapsedMilliseconds, Is.LessThan(1000), "Operation did not complete in time");
Assert.That(result, Is.Not.Null);
}

private async Task<string> ExpensiveOperation()
{
// Simulate an expensive operation
int duration = _random.Next(500, 1500);
await Task.Delay(duration); // Simulate work
return "Actual Result"; // Simulate a response
}
}
#endregion

#region RetryWithRetryExceptions
[TestFixture]
public sealed class Retry
{
private int _delayInMilliseconds;

[OneTimeSetUp]
public void Setup()
{
_delayInMilliseconds = 2500;
}

[Test]
[Retry(5, RetryExceptions = [typeof(OperationCanceledException)])]
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The retry count of 5 seems excessive for a cancellation timeout scenario. Since the delay decreases by 1000ms per attempt and starts at 2500ms, only 2-3 retries are needed (2500ms → 1500ms → 500ms). Consider reducing to [Retry(3)] for a more realistic example.

Suggested change
[Retry(5, RetryExceptions = [typeof(OperationCanceledException)])]
[Retry(3, RetryExceptions = [typeof(OperationCanceledException)])]

Copilot uses AI. Check for mistakes.
[CancelAfter(2000)]
public async Task QueryServiceAsync(CancellationToken cancellationToken)
{
string result = await CallExternalServiceAsync(cancellationToken);
Assert.That(result, Is.Not.Null);
}

private async Task<string> CallExternalServiceAsync(CancellationToken cancellationToken)
{
// Call an external service that may time out
int delayInMilliseconds = _delayInMilliseconds;
if (_delayInMilliseconds > 1000)
_delayInMilliseconds -= 1000; // Decrease delay for next attempt

await Task.Delay(delayInMilliseconds, cancellationToken); // Simulate a delay that may exceed
return "Actual Result"; // Simulate a response
}
}
#endregion
}
2 changes: 1 addition & 1 deletion docs/snippets/Snippets.NUnit/Snippets.NUnit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="NUnit" Version="4.5.0-alpha.0.17" />
<PackageReference Include="NUnit3TestAdapter" Version="5.2.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.11.2">
<PrivateAssets>all</PrivateAssets>
Expand Down