retry polly unit testing (xunit and moq) - c#

I have .net core weabpi (see code below). I am using polly retry policy (see policy below). I would like to unit test endpoint (getProducts) and test polly retry
I have found these examples but it is not clear how to unit test endpoint and retry policy?
services
.AddHttpClient<IProductService, ProductService>()
.AddPolicyHandler(GetRetryPolicy(3, 2));
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(int retryCount, int breakDuration)
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
.WaitAndRetryAsync(retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(breakDuration,
retryAttempt)));
}
.Net core api:
public interface IProductService
{
Task<IEnumerable<ProductResponse>> GetProducts(string productType);
}
public class ProductService: IProductService
{
private readonly HttpClient _httpClient;
public ProductService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<IEnumerable<ProductResponse>> GetProducts(string productType)
{
var response = await _httpClient.GetAsync("uri");
...
}
}

The sad truth is you can't really unit test your retry+endpoint logic and here are my reasonings why:
The retry is registered on the top of the HttpClient via the DI (AddPolicyHandler). When you are unit testing then you are not relying on the DI rather on individual components.
1.1 So, an integration test might be more suitable for this. I've already detailed how can you do that via WireMock.Net: 1, 2. The basic idea is to create a local http server (to mock the downstream system) with a predefined response sequence.
After you have defined your retry policy with the max retry count and time penalties, you can not retrieve them easily. So, from a unit testing perspective it is really hard to make sure that the policy has been defined correctly (like the delay is specified in seconds, not in minutes). I've already created a github issue for this, but unfortunately the development of the V8 got stuck.
Back to your test case. The correct way to articulate your test scenario with the given-when-then structure should be written like this
Given a faulty downstream service which returns 5XX responses
When I call GetProducts
Then it is performed 4 times (1 initial + 3 retry attempts)
This is not a unit test. It is more like a component/integration test. Why? Because even though you could create an HttpClient mock but in that case there will be no retry policy there.
There is a workaround: you could manually decorate the underlying handler with the policy via the PolicyHttpMessageHandler. But that's a bad idea in a unit test, because you basically re-implemented the DI part inside your test. And with that you would test your test arrangement code, not your production code.

Related

C#: Throttle/rate limit outgoing HTTP requests with Polly

I am developing an integration solution that accesses a rate limited API. I am performing a variety of CRUD operations on the API using multiple HTTP verbs on different endpoints (on the same server though). I have been pointed towards Polly multiple times, but I haven't managed to come up with a solution that actually works.
This is what I have in my startup:
builder.Services
.AddHttpClient("APIClient", client =>
{
client.BaseAddress = new Uri(C.Configuration.GetValue<string>("APIBaseAddress"));
})
.AddTransientHttpErrorPolicy(builder =>
builder.WaitAndRetryAsync(new []
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(15),
}));
This is just resilience to retry in case of failure. I have a RateLimit policy in a singleton ApiWrapper class:
public sealed class ApiWrapper
{
private static readonly Lazy<ApiWrapper> lazy = new Lazy<ApiWrapper>(() => new ApiWrapper());
public static ApiWrapper Instance { get { return lazy.Value; } }
private IHttpClientFactory _httpClientFactory;
public readonly AsyncRateLimitPolicy RateLimit = Policy.RateLimitAsync(150, TimeSpan.FromSeconds(10), 50); // 150 actions within 10 sec, 50 burst
private ApiWrapper()
{
}
public void SetFactory(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public HttpClient GetApiClient()
{
return _httpClientFactory.CreateClient("APIClient");
}
}
That policy is used in multiple other classes like this:
public class ApiConsumer
{
private HttpClient _httpClient = ApiWrapper.Instance.GetApiClient();
public async Task<bool> DoSomethingWithA(List<int> customerIDs)
{
foreach (int id in customerIDs)
{
HttpResponseMessage httpResponse = await ApiWrapper.Instance.RateLimit.ExecuteAsync(() => _httpClient.GetAsync($"http://some.endpoint"));
}
}
}
My expectation was that the rate limiter would not fire more requests than configured, but that does not seem to be true. From my understanding the way it works is that the rate limiter just throws an exception if there are more calls than the limit that has been configured. That's where I thought the Retry policy would come into play, so just try again after 5 or 15 seconds if it did not go through the limiter.
Then I played around a bit with Polly's Bulkhead policy, but as far as I can see that is meant to limit the amount of parallel executions.
I have multiple threads that may use different HttpClients (all created by the Factory like in the example above) with different methods and endpoints, but all use the same policies. Some threads run in parallel, some sequentially as I have to wait for their response before sending the next requests.
Any suggestions on how this can or should be achieved with Polly? (Or any other extension if there is good reason to)
Thanks again to #Neil and #Panagiotis for pointing me in the right direction. I wrongly assumed that the Polly rate limiter would actually delay API calls. I found a workaround that probably is not particularly nice, but for my purpose it does the trick.
I installed David Desmaisons RateLimiter package which is super simple to use. In my singleton I have now this:
public TimeLimiter RateLimiter = TimeLimiter.GetFromMaxCountByInterval(150, TimeSpan.FromSeconds(10));
I use this RateLimiter everywhere I make calls to an API endpoint like this:
HttpResponseMessage httpResponse = await ApiWrapper.Instance.RateLimiter.Enqueue(() => _httpClient.GetAsync($"http://some.endpoint"), _cancellationToken);
Does exactly what I originally expected from Polly.
In this post I would like to clarify things around rate limiter and rate gate
Similarity
Both concepts can be used to throttle requests.
They sit between the clients and the server and they know about the server's capacity.
Difference
The limiter as its name implies limits the transient traffic. It short-cuts the requests if there are too many.
The gate on the other hand holds/delays the requests until there is enough capacity.
Algorithms
The rate limiter usually implements the leaky-bucket or token-bucket algorithm
Polly's ratelimiter implements token bucket
The rate gate usually utilizies some queuing mechanism and timers
Sample implementation

Polly not catching Nsubstitute's mocked exception

I am using Polly's retry policy for my unsuccessful call. But it is not catching the exception and retrying.
Using:
Polly 7.2.3
.NET6.0
Nsubstitute 4.2.2
Setup:
var delay = Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromMilliseconds(RetryDelay), RetryCount);
_retryPolicy = Policy.Handle<HttpRequestException>()
.Or<CustomException>()
.OrResult<string>(response => !string.IsNullOrEmpty(response))
.WaitAndRetryAsync(delay);
Usage:
public async Task ProcessRequest()
{
var errors = await _retryPolicy.ExecuteAsync(async () => await this.RetryProcessRequest());
}
private async Task<string> RetryProcessRequest()
{
var token = await _tokenInfrastructure.GetTokenAsync();
return await _timezoneInfrastructure.ProcessRequest(token);
}
Unit test:
[Fact]
public async Task ProcessRequest_Throws()
{
string errors = _fixture.Create<string>();
var token = _fixture.Create<string>();
// Retry policy configured to retry 3 times on failed call
var expectedReceivedCalls = 4;
// this is throwing but Polly is not catching it and not retrying
_tokenInfrastructure.GetTokenAsync().Returns(Task.FromException<string>(new HttpRequestException()));
// this errors can be caught by Polly as configured and retrying
_timezoneInfrastructure.ProcessRequest(token).Returns(errors);
await _timezoneOrchestration.Awaiting(o => o.ProcessRequest()).Should()
.ThrowAsync<HttpRequestException>();
await _tokenInfrastructure.Received(expectedReceivedCalls).GetTokenAsync();
await _timezoneInfrastructure.Received(expectedReceivedCalls).ProcessRequest(Arg.Any<string>());
}
After doing Rubber duck debugging found my mistake. Actually, Polly was configured well and retrying.
this line of code was never calling because above we were getting exceptions.
return await _timezoneInfrastructure.ProcessRequest(token);
In Unit tests, it was expecting some retry calls:
_timezoneInfrastructure.Received(expectedReceivedCalls).ProcessRequest(Arg.Any<string>());
This post is not answer for the OP's question (the problem has already been addressed here). It is more like a set of suggestions (you can call it code review if you wish).
Exponential backoff
I'm glad to see that you are using the V2 of the backoff logic which utilizes the jitter in a proper way.
My only concern here is that depending on the actual values of RetryDelay and RetryCount the sleepDuration might explode: It can easily reach several minutes. I would suggest two solutions in that case:
Change factor parameter of the DecorrelatedJitterBackoffV2 from 2 (which is the default) to a lower number
Or try to top the max sleepDuration, here I have detailed one way to do that
Combined Retry logic
Without knowing what does RetryProcessRequest do, it seems like this _retryPolicy smashes two different policies into one. I might be wrong, so this section could suggest something which is not applicable for your code.
I assume this part decorates the _tokenInfrastructure.GetTokenAsync call
_retryPolicy = Policy.Handle<HttpRequestException>()
.WaitAndRetryAsync(delay);
whereas this part decorates the _timezoneInfrastructure.ProcessRequest call
_retryPolicy = Policy.Handle<CustomException>()
.OrResult<string>(response => !string.IsNullOrEmpty(response))
.WaitAndRetryAsync(delay);
Based on your naming I assume that these are different downstream systems: tokenInfrastructure, timezoneInfrastructure. I would suggest to create separate policies for them. You might want to apply different Timeout for them or use separate Circuit Breakers.
Naming
I know naming is hard and I assume your method names (ProcessRequest, RetryProcessRequest or ProcessRequest_Throws) are dumyfied for StackOverflow. If not then please try to spend some time to come up with more expressive names.
Component testing
Your ProcessRequest_Throws test is not really a unit test. It is more likely a component test. You are testing there the integration between the Polly's policy and the decorated code.
If you would test only the correctness of the policy setup or test only the decorated code (with NoOpPolicy) then they were unit tests.

Injected HttpClient ignores IHttpClientFactory configuration

I've created a custom library which automatically sets up Polly policies for specific services which depend on HttpClient.
This is done using the IServiceCollection extension methods and the typed client approach. A simplified example:
public static IHttpClientBuilder SetUpFooServiceHttpClient(this IServiceCollection services)
{
return services
.AddHttpClient<FooService>()
.AddPolicyHandler(GetRetryPolicy());
}
The example service:
public class FooService
{
private readonly HttpClient _client;
// OPTION 1
public FooService(HttpClient httpClient)
{
_client = httpClient;
}
// OPTION 2
public FooService(IHttpClientFactory httpClientFactory)
{
_client = httpClientFactory.CreateClient(GetType().Name);
}
public void DoJob()
{
var test = _client.GetAsync("http://example.com");
}
}
Fetching the service from the DI container (this is from a test project):
var services = new ServiceCollection();
services.SetUpFooServiceHttpClient();
services.AddSingleton<FooService>();
var fooService = services
.BuildServiceProvider()
.GetRequiredService<FooService>();
// Perform test
fooService.DoJob();
Note: In this test project, I also add an extra mocked handler since I'm trying to mock http status responses, but the mocked handler being present or not is identical to the Polly policy being present or not, so I omitted the mocked handler from the example code.
Notice the two different constructors in FooService. Depending on which one I comment out and which one I leave in, I get different outcomes. All other code remains untouched.
Option 1, injecting the HttpClient directly, ignores all my configuration. I get a standard http client with no Polly policy handler.
Options 2, injecting the IHttpClientFactory and requesting the client using the current type name (i.e. FooService) respect my configuration. I get a custom http client which contains the Polly policy handler (and any other handlers I may have configured, e.g. mocked handlers in my test suite)
The absence/existence of the policy handler is confirmed in both cases using debug inspection.
According to all documentation I've found on the subject, both options should be equivalent, at least in regards to the constructed HttpClient that I obtain in the end. But that is not the case here.
The documentation I find specifies that HttpClient can be injected when using typed clients:
The MSDN documentation, specifically the "typed clients" section example.
This SO answer
This blog post
I'm using a typed client but injecting a HttpClient clearly doesn't work for me.
Why does injecting a HttpClient vs injecting IHttpClientFactory work differently in my case?
In effect, you have the following two registrations for your FooService class:
services.AddHttpClient<FooService>()
services.AddSingleton<FooService>();
Because of how the DI container works behind-the-scenes, the second registration overwrites the first. If you remove the second registration, the first will be used, and so your constructor with the HttpClient parameter will be invoked.

Is it best practice to test my Web API controllers directly or through an HTTP client?

I'm adding some unit tests for my ASP.NET Core Web API, and I'm wondering whether to unit test the controllers directly or through an HTTP client. Directly would look roughly like this:
[TestMethod]
public async Task GetGroups_Succeeds()
{
var controller = new GroupsController(
_groupsLoggerMock.Object,
_uowRunnerMock.Object,
_repoFactoryMock.Object
);
var groups = await controller.GetGroups();
Assert.IsNotNull(groups);
}
... whereas through an HTTP client would look roughly like this:
[TestMethod]
public void GetGroups_Succeeds()
{
HttpClient.Execute();
dynamic obj = JsonConvert.DeserializeObject<dynamic>(HttpClient.ResponseContent);
Assert.AreEqual(200, HttpClient.ResponseStatusCode);
Assert.AreEqual("OK", HttpClient.ResponseStatusMsg);
string groupid = obj[0].id;
string name = obj[0].name;
string usercount = obj[0].userCount;
string participantsjson = obj[0].participantsJson;
Assert.IsNotNull(name);
Assert.IsNotNull(usercount);
Assert.IsNotNull(participantsjson);
}
Searching online, it looks like both ways of testing an API seem to be used, but I'm wondering what the best practice is. The second method seems a bit better because it naively tests the actual JSON response from the Web API without knowing the actual response object type, but it's more difficult to inject mock repositories this way - the tests would have to connect to a separate local Web API server that itself was somehow configured to use mock objects... I think?
Edit: TL;DR
The conclusion you should do both because each test serves a different purpose.
Answer:
This is a good question, one I often ask myself.
First, you must look at the purpose of a unit test and the purpose of an integration test.
Unit Test :
Unit tests involve testing a part of an app in isolation from its
infrastructure and dependencies. When unit testing controller logic,
only the contents of a single action are tested, not the behaviour of
its dependencies or of the framework itself.
Things like filters, routing, and model binding will not work.
Integration Test :
Integration tests ensure that an app's components function correctly
at a level that includes the app's supporting infrastructures, such as
the database, file system, and network. ASP.NET Core supports
integration tests using a unit test framework with a test web host and
an in-memory test server.
Things like filters, routing, and model binding will work.
“Best practice” should be thought of as “Has value and makes sense”.
You should ask yourself Is there any value in writing the test, or am I just creating this test for the sake of writing a test?
Let's say your GetGroups() method looks like this.
[HttpGet]
[Authorize]
public async Task<ActionResult<Group>> GetGroups()
{
var groups = await _repository.ListAllAsync();
return Ok(groups);
}
There is no value in writing a unit test for it! because what you are doing is testing a mocked implementation of _repository! So what is the point of that?!
The method has no logic and the repository is only going to be exactly what you mocked it to be, nothing in the method suggests otherwise.
The Repository will have its own set of separate unit tests where you will cover the implementation of the repository methods.
Now let's say your GetGroups() method is more than just a wrapper for the _repository and has some logic in it.
[HttpGet]
[Authorize]
public async Task<ActionResult<Group>> GetGroups()
{
List<Group> groups;
if (HttpContext.User.IsInRole("Admin"))
groups = await _repository.FindByExpressionAsync(g => g.IsAdminGroup == true);
else
groups = await _repository.FindByExpressionAsync(g => g.IsAdminGroup == false);
//maybe some other logic that could determine a response with a different outcome...
return Ok(groups);
}
Now there is value in writing a unit test for the GetGroups() method because the outcome could change depending on the mocked HttpContext.User value.
Attributes like [Authorize] or [ServiceFilter(….)] will not be triggered in a unit test.
.
Writing integration tests is almost always worth it because you want to test what the process will do when it forms part of an actual application/system/process.
Ask yourself, is this being used by the application/system?
If yes, write an integration test because the outcome depends on a combination of circumstances and criteria.
Now even if your GetGroups() method is just a wrapper like in the first implementation, the _repository will point to an actual datastore, nothing is mocked!
So now, not only does the test cover the fact that the datastore has data (or not), it also relies on an actual connection being made, HttpContext being set up properly and whether serialisation of the information works as expected.
Things like filters, routing, and model binding will also work.
So if you had an attribute on your GetGroups() method, for example [Authorize] or [ServiceFilter(….)], it will be triggered as expected.
I use xUnit for testing so for a unit test on a controller I use this.
Controller Unit Test:
public class MyEntityControllerShould
{
private MyEntityController InitializeController(AppDbContext appDbContext)
{
var _controller = new MyEntityController (null, new MyEntityRepository(appDbContext));
var httpContext = new DefaultHttpContext();
var context = new ControllerContext(new ActionContext(httpContext, new RouteData(), new ActionDescriptor()));
_controller.ControllerContext = context;
return _controller;
}
[Fact]
public async Task Get_All_MyEntity_Records()
{
// Arrange
var _AppDbContext = AppDbContextMocker.GetAppDbContext(nameof(Get_All_MeetUp_Records));
var _controller = InitializeController(_AppDbContext);
//Act
var all = await _controller.GetAllValidEntities();
//Assert
Assert.True(all.Value.Count() > 0);
//clean up otherwise the other test will complain about key tracking.
await _AppDbContext.DisposeAsync();
}
}
The Context mocker used for unit testing.
public class AppDbContextMocker
{
/// <summary>
/// Get an In memory version of the app db context with some seeded data
/// </summary>
/// <param name="dbName"></param>
/// <returns></returns>
public static AppDbContext GetAppDbContext(string dbName)
{
//set up the options to use for this dbcontext
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(dbName)
.Options;
var dbContext = new AppDbContext(options);
dbContext.SeedAppDbContext();
return dbContext;
}
}
The Seed extension.
public static class AppDbContextExtensions
{
public static void SeedAppDbContext(this AppDbContext appDbContext)
{
var myEnt = new MyEntity()
{
Id = 1,
SomeValue = "ABCD",
}
appDbContext.MyENtities.Add(myEnt);
//add more seed records etc....
appDbContext.SaveChanges();
//detach everything
foreach (var entity in appDbContext.ChangeTracker.Entries())
{
entity.State = EntityState.Detached;
}
}
}
and for Integration Testing: (this is some code from a tutorial, but I can't remember where I saw it, either youtube or Pluralsight)
setup for the TestFixture
public class TestFixture<TStatup> : IDisposable
{
/// <summary>
/// Get the application project path where the startup assembly lives
/// </summary>
string GetProjectPath(string projectRelativePath, Assembly startupAssembly)
{
var projectName = startupAssembly.GetName().Name;
var applicationBaseBath = AppContext.BaseDirectory;
var directoryInfo = new DirectoryInfo(applicationBaseBath);
do
{
directoryInfo = directoryInfo.Parent;
var projectDirectoryInfo = new DirectoryInfo(Path.Combine(directoryInfo.FullName, projectRelativePath));
if (projectDirectoryInfo.Exists)
{
if (new FileInfo(Path.Combine(projectDirectoryInfo.FullName, projectName, $"{projectName}.csproj")).Exists)
return Path.Combine(projectDirectoryInfo.FullName, projectName);
}
} while (directoryInfo.Parent != null);
throw new Exception($"Project root could not be located using application root {applicationBaseBath}");
}
/// <summary>
/// The temporary test server that will be used to host the controllers
/// </summary>
private TestServer _server;
/// <summary>
/// The client used to send information to the service host server
/// </summary>
public HttpClient HttpClient { get; }
public TestFixture() : this(Path.Combine(""))
{ }
protected TestFixture(string relativeTargetProjectParentDirectory)
{
var startupAssembly = typeof(TStatup).GetTypeInfo().Assembly;
var contentRoot = GetProjectPath(relativeTargetProjectParentDirectory, startupAssembly);
var configurationBuilder = new ConfigurationBuilder()
.SetBasePath(contentRoot)
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json");
var webHostBuilder = new WebHostBuilder()
.UseContentRoot(contentRoot)
.ConfigureServices(InitializeServices)
.UseConfiguration(configurationBuilder.Build())
.UseEnvironment("Development")
.UseStartup(typeof(TStatup));
//create test instance of the server
_server = new TestServer(webHostBuilder);
//configure client
HttpClient = _server.CreateClient();
HttpClient.BaseAddress = new Uri("http://localhost:5005");
HttpClient.DefaultRequestHeaders.Accept.Clear();
HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
/// <summary>
/// Initialize the services so that it matches the services used in the main API project
/// </summary>
protected virtual void InitializeServices(IServiceCollection services)
{
var startupAsembly = typeof(TStatup).GetTypeInfo().Assembly;
var manager = new ApplicationPartManager
{
ApplicationParts = {
new AssemblyPart(startupAsembly)
},
FeatureProviders = {
new ControllerFeatureProvider()
}
};
services.AddSingleton(manager);
}
/// <summary>
/// Dispose the Client and the Server
/// </summary>
public void Dispose()
{
HttpClient.Dispose();
_server.Dispose();
_ctx.Dispose();
}
AppDbContext _ctx = null;
public void SeedDataToContext()
{
if (_ctx == null)
{
_ctx = _server.Services.GetService<AppDbContext>();
if (_ctx != null)
_ctx.SeedAppDbContext();
}
}
}
and use it like this in the integration test.
public class MyEntityControllerShould : IClassFixture<TestFixture<MyEntityApp.Api.Startup>>
{
private HttpClient _HttpClient;
private const string _BaseRequestUri = "/api/myentities";
public MyEntityControllerShould(TestFixture<MyEntityApp.Api.Startup> fixture)
{
_HttpClient = fixture.HttpClient;
fixture.SeedDataToContext();
}
[Fact]
public async Task Get_GetAllValidEntities()
{
//arrange
var request = _BaseRequestUri;
//act
var response = await _HttpClient.GetAsync(request);
//assert
response.EnsureSuccessStatusCode(); //if exception is not thrown all is good
//convert the response content to expected result and test response
var result = await ContentHelper.ContentTo<IEnumerable<MyEntities>>(response.Content);
Assert.NotNull(result);
}
}
Added Edit:
In conclusion, you should do both, because each test serves a different purpose.
Looking at the other answers you will see that the consensus is to do both.
TL;DR
Is it best practice to test [...] directly or through an HTTP client?
Not "or" but "and". If you serious about best practices of testing - you need both tests.
First test is a unit test. But the second one is an integration test.
There is a common consensus (test pyramid) that you need more unit tests comparing to the number of integration tests. But you need both.
There are many reasons why you should prefer unit tests over integration tests, most of them boil down to the fact that unit test are small (in all senses) and integration tests - aren't. But the main 4 are:
Locality
When your unit test fails, usually, just from it's name you can figure out the place where the bug is. When integration test becomes red, you can't say right away where is the issue. Maybe it's in the controller.GetGroups or it's in the HttpClient, or there is some issue with the network.
Also, when you introduce a bug in your code it's quite possible that only one of unit tests will become red, while with integration tests there are more chances that more than one of them will fail.
Stability
With a small project which you can test on you local box you probably won't notice it. But on a big project with distributed infrastructure you will see blinking tests all the time. And that will become a problem. At some point you can find yourself not trusting test results anymore.
Speed
With a small project with a small number of tests you won't notice it. But on a bit project it will become a problem. (Network delays, IO delays, initialization, cleanup, etc., etc.)
Simplicity
You've noticed it yourself.
But that not always true. If you code is poorly structured, then it's easier to write integration tests. And that's one more reason why you should prefer unit tests. In some way they force you to write more modular code (and I'm not taking about Dependency Injection).
But also keep in mind that best practices are almost always about big projects. If your project is small, and will stay small, there are a big chance that you'll be better off with strictly opposite decisions.
Write more tests. (Again, that means - both). Become better at writing tests. Delete them latter.
Practice makes perfect.
If we limit the scope of discussion to Controller vs HttpClient testing comparison, I would say that it is better to use HttpClient. Because if you write tests for your controllers, you're already writing integration tests already and there is almost no point to write "weaker" integration tests while you can write stronger ones that is more realistic and also superset of the weaker ones.
For example, you can see from your own example that both of your test are testing exactly the same functionality. The different is that the latter one cover more area of testing -- JSON response, or can be something else like HTTP header you want to test. If you write the latter test, you don't need the first test at all.
I understand the pain of how to inject mocked dependencies. This requires more effort comparing to testing controller directly. However, .NET Core already provides a good set of tools to help you on that. You can setup the test host inside the test itself, configure it and get HttpClient from it. Then you can use that HttpClient for your testing purpose.
The other concern is that it is quite a tedious task to craft HttpClient's request for each test. Anyway, Refit can help you a lot on this. Refit's declarative syntax is quite easy to understand (and maintain eventually). While I would also recommend Refit for all remote API calls, it is also suitable for ASP.NET Core integration testing.
Combining all solutions available, I don't see why you should limit to controller test while you can go for more "real" integration test with only some little more effort.
I never have liked mocking in that as applications mature the effort spent on mocking can make for a ton of effort.
I like exercising endpoints by direct Http calls. Today there are fantastic tools like Cypress which allow the client requests to be intercepted and altered. The power of this feature along with easy Browser based GUI interaction blurs traditional test definitions because one test in Cypress can be all of these types Unit, Functional, Integration and E2E.
If an endpoint is bullet proof then error injection becomes impossible from outside. But even errors from within are easy to simulate. Run the same Cypress tests with Db down. Or inject intermittent network issue simulation from Cypress. This is mocking issues externally which is closer to a prod environment.
When doing unit test, it is important to know what are you going to test and write the tests based on your requirements. However, the second test, might looks like an integration test instead of an unit test, but I do not care to this point now!
Between your tests, I would recommend you to use the second option, because in the second unit test, you are testing your WebApi, as an WebApi, not as a class. For example suppose that you have a class with a method named X(). So how likely is it to write an unit test for it using Reflection? If it is completely unlikely, then writing an unit test based on Reflection is a waste of time. If it is likely, so you should write your test using Reflection too.
Moreover, using the second approach you are able to change the tech stack(For replace .Net with php) used for producing the WebApi, without changing you tests(This is what we expect from a WebApi too).
Finally, you should make a decision! how are you going to use this WebApi? How likely is it to call your WebApi using direct class instantiating?
Note:
It might be irrelevant to your question, but you should concentrate on your Asserts, too. For example asserting ResponseStatusCode and ResponseStatusMsg might not be needed and you can assert only one.
Or what will happen if obj is null? or obj has more than one member?
I'd say that they are not mutually exclusive. The first option is a classical unit test while the second is an integration test as involves more than a single unit of code.
If I had time to write either unit tests or integration tests, I'd pick unit tests as provides a more focused approach and gives, at least in my opinion, the best result from cost benefit.
In some particular projects where I had enough resources to write different suites of tests, I wrote both tests covering approaches. Where the second one would run without mocking anything (or maybe just the persistent storage) so I could test how all the components integrate together.
In relation to good practices, if you want to do real unit test, then you have no option but picking option one as no external dependencies are allowed (HttpClient is an external dependency).
Then, if the time and resources allow it, you could do integration testing for the most critical and/or complex paths.
If you are looking for some non programming You can use Postman, and can create collection of requests and can test multiple requests one by one.
You can use Swagger (aka OpenAPI).
Install Swashbuckle.AspNetCore from nuget.
using Microsoft.OpenApi.Models;
//in Startup.ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});
}
//in Startup.Configure
public void Configure(IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
Finally, add "launchUrl": "swagger", in launchSettings.json

Should Polly Policies be singletons?

I have a query, IGetHamburgers, that calls an external API.
I've registered the implementation of IGetHamburgers in my DI container as a Singleton. Im using Polly as a Circuitbreaker, if two requests fails the circuit will open.
My goal is that all calls to the Hamburger api should go through the same circuitbreaker, if GetHamburgers fails, then all other calls should fail as well.
How should I use my Policy? Should I register my Policy as a field like this:
private Policy _policy;
private Policy Policy
{
get
{
if(this_policy != null)
{
return this_policy;
}
this._policy = Policy
.Handle<Exception>()
.CircuitBreaker(2, TimeSpan.FromMinutes(1));
return this._policy;
}
}
public object Execute(.......)
{
return Policy.Execute(() => this.hamburgerQuery.GetHamburgers());
}
OR
public object Execute(.......)
{
var breaker = Policy
.Handle<Exception>()
.CircuitBreaker(2, TimeSpan.FromMinutes(1));
return breaker.Execute(() => this.hamburgerQuery.GetHamburgers());
}
I guess that the first option is the correct way since then the Policy object will always be the same and can keep track of the exception count and stuff like that.
My question is, will option number two work as well? I've found a lot of samples/examples on Pollys Github but I can't find any "real world" examples where Polly is used together with DI and stuff like that?
I guess that the first option is the correct way since then the Policy object will always be the same and can keep track of the exception count and stuff like that.
Correct. This is described in the Polly wiki here. In brief:
Share the same breaker policy instance across call sites when you want those call sites to break in common - for instance they have a common downstream dependency.
Don't share a breaker instance across call sites when you want those call sites to have independent circuit state and break independently.
See this stackoverflow answer for a more extensive discussion of configuring policies separately from their usage, injecting them to usage sites by DI, and the effects of re-using the same instance (for example a singleton) versus using separate instances, across the full range (at June 2017) of Polly policies.
will option number two work as well?
No (for the converse reason: each call creates a separate instance, so won't share circuit statistics/states with other calls).

Categories