From 2e8296065ae4276d33fa94375cc75eecfdac3020 Mon Sep 17 00:00:00 2001 From: martintmk <103487740+martintmk@users.noreply.github.com> Date: Fri, 29 Sep 2023 14:30:10 +0200 Subject: [PATCH] [Docs] Mocking of `ResiliencePipelineProvider` (#1662) --- docs/advanced/testing.md | 72 ++++++++++++++++++++++++++++++++++++ src/Snippets/Docs/Testing.cs | 67 ++++++++++++++++++++++++++++++++- src/Snippets/Snippets.csproj | 2 + 3 files changed, 140 insertions(+), 1 deletion(-) diff --git a/docs/advanced/testing.md b/docs/advanced/testing.md index 94035e076d..0f291930ec 100644 --- a/docs/advanced/testing.md +++ b/docs/advanced/testing.md @@ -61,3 +61,75 @@ ResiliencePipelineDescriptor descriptor = pipeline.GetPipelineDescriptor(); // ... ``` + +## Mocking `ResiliencePipelineProvider` + +Consider the following code that might resemble a part of your project: + + +```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 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(); + } +} +``` + + +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` and return an empty pipeline: + + +```cs +ResiliencePipelineProvider pipelineProvider = Substitute.For>(); + +// 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 +``` + + +This example leverages the [`NSubstitute`](https://github.com/nsubstitute/NSubstitute) library to mock the pipeline provider. diff --git a/src/Snippets/Docs/Testing.cs b/src/Snippets/Docs/Testing.cs index 88842e6d97..472e5b4e2d 100644 --- a/src/Snippets/Docs/Testing.cs +++ b/src/Snippets/Docs/Testing.cs @@ -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; @@ -58,4 +61,66 @@ public static void GetPipelineDescriptorGeneric() #endregion } + + public static void PipelineProviderProviderMocking() + { + #region testing-resilience-pipeline-provider-mocking + + ResiliencePipelineProvider pipelineProvider = Substitute.For>(); + + // 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 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(); + } } + +#endregion diff --git a/src/Snippets/Snippets.csproj b/src/Snippets/Snippets.csproj index 0140e704eb..0dbab59080 100644 --- a/src/Snippets/Snippets.csproj +++ b/src/Snippets/Snippets.csproj @@ -9,6 +9,7 @@ false false $(NoWarn);SA1123;SA1515;CA2000;CA2007;CA1303;IDE0021;IDE0017;IDE0060;CS1998;CA1064;S3257;IDE0028;CA1031;CA1848 + $(NoWarn);RS0016;SA1402;SA1600;RS0037;CA1062;SA1204 Snippets false @@ -23,6 +24,7 @@ +