Skip to content
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

[Docs] Mocking of ResiliencePipelineProvider #1662

Merged
merged 3 commits into from
Sep 29, 2023
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
72 changes: 72 additions & 0 deletions docs/advanced/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,75 @@ ResiliencePipelineDescriptor descriptor = pipeline.GetPipelineDescriptor();
// ...
```
<!-- endSnippet -->

## Mocking `ResiliencePipelineProvider<TKey>`

Consider the following code that might resemble a part of your project:

<!-- snippet: testing-resilience-pipeline-provider-usage -->
```cs
// Represents an arbitrary API that needs resilience support
public class MyApi
{
private readonly ResiliencePipeline _pipeline;

// The value of pipelineProvider is injected via dependency injection
public MyApi(ResiliencePipelineProvider<string> pipelineProvider)
{
_pipeline = pipelineProvider.GetPipeline("my-pipeline");
}

public async Task ExecuteAsync(CancellationToken cancellationToken)
{
await _pipeline.ExecuteAsync(
static async token =>
{
// Add your code here
},
cancellationToken);
}
}

// Extensions to incorporate MyApi into dependency injection
public static class MyApiExtensions
{
public static IServiceCollection AddMyApi(this IServiceCollection services)
{
return services
.AddResiliencePipeline("my-pipeline", builder =>
{
builder.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 4
});
})
.AddSingleton<MyApi>();
}
}
```
<!-- endSnippet -->

In the example above:

- The `MyApi` class is introduced, representing part of your application that requires resilience support.
- The `AddMyApi` extension method is also defined, which integrates `MyApi` into dependency injection (DI) and sets up the resilience pipeline it uses.

For unit tests, if you want to assess the behavior of `ExecuteAsync`, it might not be practical to rely on the entire pipeline, especially since it could slow down tests during failure scenario evaluations. Instead, it's recommended to mock the `ResiliencePipelineProvider<string>` and return an empty pipeline:

<!-- snippet: testing-resilience-pipeline-provider-mocking -->
```cs
ResiliencePipelineProvider<string> pipelineProvider = Substitute.For<ResiliencePipelineProvider<string>>();

// Mock the pipeline provider to return an empty pipeline for testing
pipelineProvider
.GetPipeline("my-pipeline")
.Returns(ResiliencePipeline.Empty);

// Use the mocked pipeline provider in your code
var api = new MyApi(pipelineProvider);

// You can now test the api
```
<!-- endSnippet -->

This example leverages the [`NSubstitute`](https://github.com/nsubstitute/NSubstitute) library to mock the pipeline provider.
67 changes: 66 additions & 1 deletion src/Snippets/Docs/Testing.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Polly.Retry;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using Polly.Registry;
using Polly.Retry;
using Polly.Testing;
using Polly.Timeout;
using Xunit;
Expand Down Expand Up @@ -58,4 +61,66 @@ public static void GetPipelineDescriptorGeneric()

#endregion
}

public static void PipelineProviderProviderMocking()
{
#region testing-resilience-pipeline-provider-mocking

ResiliencePipelineProvider<string> pipelineProvider = Substitute.For<ResiliencePipelineProvider<string>>();

// Mock the pipeline provider to return an empty pipeline for testing
pipelineProvider
.GetPipeline("my-pipeline")
.Returns(ResiliencePipeline.Empty);

// Use the mocked pipeline provider in your code
var api = new MyApi(pipelineProvider);

// You can now test the api

#endregion
}
}

#region testing-resilience-pipeline-provider-usage

// Represents an arbitrary API that needs resilience support
public class MyApi
{
private readonly ResiliencePipeline _pipeline;

// The value of pipelineProvider is injected via dependency injection
public MyApi(ResiliencePipelineProvider<string> pipelineProvider)
{
_pipeline = pipelineProvider.GetPipeline("my-pipeline");
}

public async Task ExecuteAsync(CancellationToken cancellationToken)
{
await _pipeline.ExecuteAsync(
static async token =>
{
// Add your code here
},
cancellationToken);
}
}

// Extensions to incorporate MyApi into dependency injection
public static class MyApiExtensions
{
public static IServiceCollection AddMyApi(this IServiceCollection services)
{
return services
.AddResiliencePipeline("my-pipeline", builder =>
{
builder.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 4
});
})
.AddSingleton<MyApi>();
}
}

#endregion
2 changes: 2 additions & 0 deletions src/Snippets/Snippets.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<IsPackable>false</IsPackable>
<NoWarn>$(NoWarn);SA1123;SA1515;CA2000;CA2007;CA1303;IDE0021;IDE0017;IDE0060;CS1998;CA1064;S3257;IDE0028;CA1031;CA1848</NoWarn>
<NoWarn>$(NoWarn);RS0016;SA1402;SA1600;RS0037;CA1062;SA1204</NoWarn>
<RootNamespace>Snippets</RootNamespace>
<EnablePackageValidation>false</EnablePackageValidation>
</PropertyGroup>
Expand All @@ -23,6 +24,7 @@
<ProjectReference Include="..\Polly.RateLimiting\Polly.RateLimiting.csproj" />
<ProjectReference Include="..\Polly.Testing\Polly.Testing.csproj" />
<ProjectReference Include="..\Polly\Polly.csproj" />
<PackageReference Include="NSubstitute" />

<Using Include="Polly" />
</ItemGroup>
Expand Down