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

Testability Issue #10309

Closed
carbo-will opened this issue Feb 4, 2019 — with docs.microsoft.com · 8 comments
Closed

Testability Issue #10309

carbo-will opened this issue Feb 4, 2019 — with docs.microsoft.com · 8 comments
Assignees
Labels
dotnet-architecture/svc microservices/subsvc support-request Support-style question;customer needs help solving a problem [org][type][category]

Comments

Copy link

carbo-will commented Feb 4, 2019

How would you write unit tests for a class that has HttpClient injected in its constructor (such as the CatalogService example above)? I would prefer if there were an interface (something called IHttpClient, or maybe IHttpMessageInvoker) that could be injected instead. Then providing a mock implementation for a unit test would be more straightforward.


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

Copy link

rtlayzell commented Mar 25, 2019

I second this, while being able to control the life-time of the handlers is interesting, injecting the HttpClient itself isn't testable. We usually opt for passing in an HttpMessageHandler and creating an instance of HttpClient from it inside the service class itself. This way the service classes are testable simply by replacing the HttpClientHandler with some mock/test handler, and we're also granted a huge amount of flexibility when it comes to handling the requests.

Copy link

malu-mn commented Mar 27, 2019

If you are injecting concrete implementation in the constructor, the entire service I can't test!. Please give a solution to it. We have segregated all the the http call logic to a separate call, that will be injected to other services. Thus reducing the un-testable code to minimum. But there should be a better way to do it.

Copy link

I think if you want to test your class that takes the httpclient, then just create an instance of httpclient using the overloaded constructor and add your own faked HttpMessageHandler like you would in any other case.

I think where I get a little confused if I wanted to test Polly in this case. The polly extensions mentioned in the next section are put together as part of the factory operation, but how can I do a setup to simulate the error to test that my httpclient is using polly policies and doing things like using fallbacks, and so on?

@mvelosop
Copy link
Contributor

mvelosop commented Aug 8, 2019

Hi, thanks for your feedback.

Regrettably (or not) there's no IHttpClient in .NET so there's not much we can do in that direction.

You can use the option that @jstafford5380 mentions, however it might be a bit cumbersome to setup.

I've used another approach that I've found to be quite simple to mock and, IMHO, much more clear than using HttpClient.

Just so you might give it a try:

  1. Create an specialized client, say PaymentProcessClient, that receives an HttpClient through the constructor.

  2. PaymentProcessClient' only has methods relevant to the business, say NotifyPaymentResultAsync()` with whatever (business) parameters it needs.

  3. The methods would actually use the HttpClient.

  4. Make the methods virtual.

This way, you'd inject PaymentProcessClient in your class instead of HttpClient, and you could easily mock it and configure the virtual methods to return whatever you need for your tests.

The HttpClient registration would then be something like:

services.AddHttpClient<PaymentProcessClient>(client =>
{
    client.BaseAddress = new Uri(Configuration["App:BaseUrl"]);
});

But now you want/have to test PaymentProcessClient, so you have two options here:

  1. Use @jstafford5380's method
  2. Use ASP.NET Core integration tests

About the testing of Polly strategies, what I've thought about, but haven't actually implemented yet (I might in a few days), is to use integration testing, deploy the services to Docker containers and control the connection/reconnection to the "remote" services being tested by accessing the local Docker REST API endpoints for controlling the network connections.

Hope this helps.

@mvelosop mvelosop self-assigned this Aug 8, 2019
@mvelosop
Copy link
Contributor

mvelosop commented Aug 8, 2019

Hey @jstafford5380, just learned about Simmy a Polly companion to test for resiliency.

This looks quite interesting!

Although, at least for some initial testing, it's perhaps better to have some "deterministic" failures.

Cheers.

Copy link

I am using RichardSzalay.MockHttp for this.

        // Arrange
        var mockHttpMessageHandler = new MockHttpMessageHandler();
        var request = mockHttpMessageHandler.When("http://example.com/api/doMagic").Respond(HttpStatusCode.OK);

        var mockHttpClient = mockHttpMessageHandler.ToHttpClient();
        mockHttpClient.BaseAddress = new Uri("http://example.com");

        var myClassUsingHttpClient = new ClassUsingHttpClient(mockHttpClient);

...
mockHttpMessageHandler.GetMatchCount(request).Should().Be(1);

@jstafford5380
Copy link

jstafford5380 commented Sep 2, 2019

Hey @jstafford5380, just learned about Simmy a Polly companion to test for resiliency.

This looks quite interesting!

Although, at least for some initial testing, it's perhaps better to have some "deterministic" failures.

Cheers.

At the time of my suggestion, I hadn't been using .AddHttpClient<T>. That said, I actually found out later on that there is a MS package for Polly with a method that can be chained off of that AddHttpClient registration, allowing you to add a policy to the client.

https://www.nuget.org/packages/Microsoft.Extensions.Http.Polly/

Under the hood, it basically does what I was recommending which was adding a custom handler to the client.

For the testing, the nice thing about what I was recommending is that you can setup your fake handler to behave exactly how you need it to behave in order to setup your test. What I ended up doing is coding my service classes/facades to take HttpClient. Then I register it with .AddHttpClient<TServiceInterface, TImplementation>() which will both ensure the service gets a configured HttpClient and also register the service with DI. So now, since the service already takes HttpClient as a ctor argument, the tests just need me to create an http client with a faked handler already added.

// my fake handler lets me set the response status code and optional response body
// it counts tracks calls to `Send(...)` but make it do whatever you need
var fakeHandler = new MyFakedHandler(200, someTestResponseBody);
var client = new HttpClient(fakeHandler);
var service = new MyService(client);

// assume this service facade fails if 200 is not returned 
var result = service.DoThing(args);

// assert whatever makes sense
Assert.Equal(1, fakeHandler.CallCount);
Assert.NotNull(result);

@mvelosop
Copy link
Contributor

Thanks @GitMarJansen, @jstafford5380, I find both approaches quite interesting! 👍

I'm adding a link from eShopOnContainers wiki, because this is an interesting discussion to keep referenced. 😊

I think the original question has been handled so I'm closing this issue now, but feel free to comment, will reopen if needed.

@BillWagner BillWagner added support-request Support-style question;customer needs help solving a problem [org][type][category] and removed ⌚ Not Triaged Not triaged labels Sep 25, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dotnet-architecture/svc microservices/subsvc support-request Support-style question;customer needs help solving a problem [org][type][category]
Projects
None yet
Development

No branches or pull requests

9 participants