dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.18k stars 4.72k forks source link

An simple way to mock an httpClient.GetAsync(..) method for unit tests? #14535

Closed PureKrome closed 4 years ago

PureKrome commented 9 years ago

System.Net.Http has now been uploaded to the repo :smile: :tada: :balloon:

Whenever I've used this in some service, it works great but makes it hard to unit test => my unit tests don't want to actually ever hit that real end point.

Ages ago, I asked @davidfowl what should we do? I hoping I paraphrase and don't misquote him - but he suggested that I need to fake up a message handler (ie. HttpClientHandler), wire that up, etc.

As such, I ended up making a helper library called HttpClient.Helpers to help me run my unit tests.

So this works ... but it feels very messy and .. complicated. I'm sure I'm not the first person that needs to make sure my unit tests don't do a real call to an external service.

Is there an easier way? Like .. can't we just have an IHttpClient interface and we can inject that into our service?

richardszalay commented 9 years ago

Just got notified of this thread, so thought I'd throw my 2c in.

I use dynamic mocking frameworks as much as the next guy, but in recent years I've come to appreciate that some domains (like HTTP) are better served being faked behind something with more domain knowledge. Anything beyond basic URL matching would be pretty painful using dynamic mocking, and as you iterate on it you'd end up with your own mini version of what MockHttp does anyway.

Take Rx as another example: IObservable is an interface and therefore mockable, but it would be insanity to test complex compositions (not to mention schedulers) with dynamic mocking alone. The Rx testing library gives much more semantic meaning to the domain in which it works.

Back on HTTP specifically, albeit in the JS world, you can absolutely also use sinon to mock XMLHttpRequest, rather than use Angular's testing APIs, but I'd be pretty surprised if anyone did.

Having said all that, I totally agree about discoverability. Angular's HTTP testing documentation is right there with its HTTP documentation, but you'd need to know about MockHttp to look for it. As such, I'd be totally ok with contributing MockHttp back into the main library, if any of the CoreFx team wants to get in contact (the licenses are already the same).

malixsys commented 9 years ago

I like mocking it but we also use a Func signature:

public static async Task<T> GetWebObjectAsync<T>(Func<string, Task<string>> getHtmlAsync, string url)
{
    var html = await getHtmlAsync(url);
    var info = JsonConvert.DeserializeObject<T>(html);
    return info;
}

This allows for Test class code to be this simple:

Func<string, Task<string>> getHtmlAsync = u => Task.FromResult(EXPECTED_HTML);
var result = controller.InternalCallTheWeb(getHtmlAsync);

And the controller can simply do:

public static HttpClient InitHttpClient()
{
    return _httpClient ?? (_httpClient = new HttpClient(new WebRequestHandler
    {
        CachePolicy = new HttpRequestCachePolicy(HttpCacheAgeControl.MaxAge, new TimeSpan(0, 1, 0)),
        Credentials = new NetworkCredential(PublishingCredentialsUserName, PublishingCredentialsPassword)
    }, true));
}

[Route("Information")]
public async Task<IHttpActionResult> GetInformation()
{
    var httpClient = InitHttpClient();
    var result = await InternalCallTheWeb(async u => await httpClient.GetStringAsync(u));
    ...
}

But they COULD make it a bit easier... :)

PureKrome commented 9 years ago

Hi .NET team! Just touching base on this issue. It's been around for a month+ now and just curious to any thoughts on what the team thinks about this discussion.

There's been various points from both sides of the camp - all interesting and relevant IMO.

Are there any updates from you gals/guys?

ie.

  1. We're read the convo and will not change a thing. Thanks for your input, have a lovely day. Here, have a piece of :cake:
  2. We honestly haven't thought about it (things are hella-busy around here, as you could imagine) but will think about it at a later date before we go Release-To-Web (RTW).
  3. Actually, we have had some internal discussions about it and we acknowledge that the public is finding it a wee bit painful to grok. So we are thinking we might do XXXXXXXXX.

ta!

davidsh commented 9 years ago

It's part 2 and 3.

One way to simplify the ability to insert a "mock" handler to help with testing is for a static method to be added to HttpClient, i.e. HttpClient.DefaultHandler. This static method will allow developers to set a different default handler than the current HttpClientHandler when calling the default HttpClient() constructor. In addition to helping with testing against a "mock" network, it will help developers that write code to portable libraries above HttpClient, where they don't directly create HttpClient instances. So, it would allow them to "inject" this alternate default handler.

So, this is one example of something we can add to the current object model that would be additive and not a breaking change to the current architecture.

PureKrome commented 9 years ago

Thanks heaps @davidsh for the very prompt reply :+1:

I'll happily wait and watch this space as it's exciting to know that it's not part 1 :smile: .. and look forward to seeing whatever ever changes happen :+1:

Thanks again (team) for your patience and listening -- really really appreciate it!

/me happily waits.

luisrudge commented 9 years ago

@davidsh so, the interface os virtual methods are out of question?

davidsh commented 9 years ago

It is not ouf of the question. But short term adding interfaces as was suggested in this issue thread needs careful study and design. It is not easily mapped to the existing object model of HttpClient and potentially would be a breaking change to the API surface. So, it is not something that is quick or easy to design/implement.

luisrudge commented 9 years ago

What about virtual methods?

davidsh commented 9 years ago

Yes, adding virtual methods is not a breaking change but an additive one. We are still working out what design changes are feasible in this area.

PureKrome commented 9 years ago

/me casts Resurrection upon this thread...

~ thread twists, groans, twiches and comes alive!


Just was listening to Aug20th 2015 Community StandUp and they mentioned that HttpClient will be avail cross plat with Beta7. YAY!

So ... has there been any decision's on this? Not suggesting either/or .. just politely asking for any discussion updates, etc?

davidsh commented 9 years ago

@PureKrome There has been no decision on this but this API design/investigation work is folded into our future plans. Right now, the majority of us are very focused on getting the rest of the System.Net code onto GitHub and working cross-platform. This includes both existing source code and porting our tests to the Xunit framework. Once this effort converges, the team will have more time to begin focusing on future changes like what is suggested in this thread.

PureKrome commented 9 years ago

Thanks heaps @davidsh - I really appreciate your time to reply :) Keep up the awesome work! :balloon:

metzgithub commented 8 years ago

Isn't using mocks for testing http services a bit obsolete? There are quite a few self hosting options what allow easy mocking of the http service and requires no code changes, only a change in service url. I.e. HttpListener and OWIN self host, but for complex API one could even build a mock service or use an autoresponder.

PureKrome commented 8 years ago

image

image

reyou commented 8 years ago

Any update on unit-test-ability of this HttpClient class? I am trying to Mock DeleteAsync method but as mentioned earlier Moq is complaining about function is not virtual. Seems there is no easier way other than creating our own wrapper.

YehudahA commented 8 years ago

I vote for Richardszalay.Mockhttp! It's excellent! Simply and brilliant!

https://github.com/richardszalay/mockhttp

PureKrome commented 8 years ago

but @YehudahA - we shouldn't need that.... (that's the point of this convo) :)

YehudahA commented 8 years ago

@PureKrome we shouldn't need IHttpClient. It's the same idea as EF 7. You never use IDbContext or IDbSet, but you just change the behavior using DbContextOptions..

Richardszalay.MockHttp is an out-of-the-box solution.

JonHanna commented 8 years ago

It's an out-of-another-box solution.

PureKrome commented 8 years ago

we shouldn't need IHttpClient. It's the same idea as EF 7. You never use IDbContext or IDbSet, but you just change the behavior using DbContextOptions.

That's ok - I totally disagree, so we agree to disagree then.

Do both ways work? yes. Some people (like myself) find mocking an interface or virtual method EASIER for reasons like discoverability and readability and simplisity.

Others (like yourself) like to manipulate the behaviour. I find that more complex, harder to discover and more time consuming. (which (to me) translates to harder to support/maintain).

Each to their own, I guess :)

YehudahA commented 8 years ago

Richardszalay says: "This pattern is heavily inspired by AngularJS's $httpBackend". That's right. Let see here: https://docs.angularjs.org/api/ngMock/service/$httpBackend

metzgithub commented 8 years ago

@PureKrome Yes it is simpler. You don't have to know what classes and methods are used, you don't need a mock library/interfaces/virtual methods, the setup will not change if you switch from HttpClient to something else.

        private static IDisposable FakeService(string uri, string response)
        {
            var httpListener = new HttpListener();
            httpListener.Prefixes.Add(uri);

            httpListener.Start();

            httpListener.GetContextAsync().ContinueWith(task =>
            {
                var context = task.Result;

                var buffer = Encoding.UTF8.GetBytes(response);

                context.Response.OutputStream.Write(buffer, 0, buffer.Length);

                context.Response.OutputStream.Close();
            });

            return httpListener;
        }

Usage:

            var uri = "http://localhost:8888/myservice/";
            var fakeResponse = "Hello World";

            using (FakeService(uri, fakeResponse))
            {
                // Run your test here ...

                // This is only to test that is really working:
                using (var client = new HttpClient())
                {
                    var result = client.GetStringAsync(uri).Result;
                }
            }
DinisCruz commented 8 years ago

@metzgithub is the best one here, since it allows for proper simulation of the behaviour of the target service.

It is exactly what I was looking for since it gives me the perfect place to put 'mal-formed/malicious' data (for security and non-happy-path tests)

mike-hanson commented 8 years ago

I'm late to this thread, I arrived here after searching to see how people unit test their dependence on HttpClient, and found some very inventive ways of doing so. I will state up front that in this argument I would come down firmly on the side for having in interface. However the rub for me is that I don't have a choice.

I choose to design and develop components and services that declare their dependency on other components. I strongly prefer the choice to declare those dependencies via a constructor and at run time rely on an IoC container to inject a concrete implementation and control at this point whether it is a singleton or a transient instance.

I choose to use a definition of testability that I learned around a decade ago while embracing the then relatively new practice of Test Driven Development:

"A class is testable if it can be removed from it's normal environment and still work when all it's dependencies are injected"

Another choice I prefer to make is to test the interaction of my components with those they depend on. If I declare a dependency on a component I want to test that my component uses it correctly.

IMHO a well designed API allows me to work and code the way I choose, some technical constraints are acceptable. I see having only a concrete implementation of a component like HttpClient as depriving me of choice.

So I am left with the choice to resort to my usual solution in these cases and create a lightweight adapter/wrapper library that gives me an interface and allows me to work the way I choose.

jdcrutchley commented 8 years ago

I'm a little late to the party too, but even after reading all of the preceding comments, I'm still not sure how to proceed. Can you please tell me how I would verify that particular methods in my object under test call particular endpoints on a public API? I don't care what comes back. I just want to make sure that when someone calls "GoGetSomething" on an instance of my ApiClient class, that it sends a GET to "https://somenifty.com/api/something". What I'd LIKE to do is (not working code):

Mock<HttpClient> mockHttpClient = new Mock<HttpClient>();
mockHttpClient.Setup(x => x.SendAsync(It.IsAny<HttpRequestMessage>())).Verifiable();
ApiClient client = new ApiClient(mockHttpClient.Object);

Something response = await client.GoGetSomething();
mockHttpClient
    .Verify(x => x.SendAsync(
        It.Is<HttpRequestMessage>(m =>
            m.Method.Equals(HttpMethod.Get) &&
            m.RequestUri.Equals("https://somenifty.com/api/something"))));

But, it doesn't let me mock SendAsync.

IF I could get that basic thing working, then I might want to actually test how my class handles particular RETURNS from that call as well.

Any ideas? This doesn't seem (to me) like too much of an out-of-whack thing to want to test.

davidsh commented 8 years ago

But, it doesn't let me mock SendAsync.

You don't mock HttpClient.SendAsync method.

Instead you create a mock handler derived from HttpMessageHandler. Then you override the SendAsync method of that. Then you pass that handler object into the constructor of HttpClient.

YehudahA commented 8 years ago

@jdcrutchley, You can do it with @richardszalay's MockHttp.

public class MyApiClient
{
    private readonly HttpClient httpClient;

    public MyApiClient(HttpMessageHandler handler)
    {
        httpClient = new HttpClient(handler, disposeHandler: false);
    }

    public async Task<HttpStatusCode> GoGetSomething()
    {
        var response = await httpClient.GetAsync("https://somenifty.com/api/something");
        return response.StatusCode;
    }
}

[TestMethod]
public async Task MyApiClient_GetSomething_Test()
{
    // Arrange
    var mockHandler = new MockHttpMessageHandler();
    mockHandler.Expect("https://somenifty.com/api/something")
        .Respond(HttpStatusCode.OK);

    var client = new MyApiClient(mockHandler);

    // Act
    var response = await client.GoGetSomething();

    // Assert
    Assert.AreEqual(response, HttpStatusCode.OK);
    mockHandler.VerifyNoOutstandingExpectation();
}
PureKrome commented 8 years ago

@jdcrutchley or you create your OWN wrapper class and wrapper interface with one method SendAsync .. which just calls the real httpClient.SendAsync (in your wrapper class).

I'm guessing that's what @richardszalay 's MockHttp is doing.

That's the general pattern people do when they can't mock something (because there's no interface OR it's not virtual).

richardszalay commented 8 years ago

mockhttp is a mock DSL on HttpMessageHandler. It's no different to dynamically mocking it using Moq, but the intent is clearer due to the DSL.

The experience is the same either way: mock HttpMessageHandler and create a concrete HttpClient from it. Your domain component depends on either HttpClient or HttpMessageHandler.

Edit: TBH, I'm not entirely sure what the problem with the existing design is. The difference between "mockable HttpClient" and "mockable HttpMessageHandler" is literally one line of code in your test: new HttpClient(mockHandler), and it has the advantage that the implementation maintains the same design for "Different implementation per platform" and "Different implementation for testing" (ie. is not tainted for the purposes of test code).

The existence of libraries like MockHttp only serves to expose a cleaner, domain-specific, test API. Making HttpClient mockable via an interface would not change this.

KenRoytman commented 8 years ago

As a newcomer to the HttpClient API, hopefully my experience will add value to the discussion.

I was also surprised to learn that HttpClient did not implement an interface for easy mocking. I searched around the web, found this thread, and read it from start to finish before proceeding. The answer from @drub0y convinced me to proceed down the route of simply mocking HttpMessageHandler.

My mocking framework of choice is Moq, probably pretty standard for .NET devs. After spending close to an hour fighting the Moq API with trying to setup and verify the protected SendAsync method, I finally hit a bug in Moq with verifying protected generic methods. See here.

I decided to share this experience to support the argument that a vanilla IHttpClient interface would've made life much easier and saved me a couple of hours. Now that I've hit a dead-end, you can add me to the list of devs that have written a simple wrapper/interface combo around HttpClient.

richardszalay commented 8 years ago

That's an excellent point Ken, I had forgotten that HttpMessageHandler.SendAsync was protected.

Still, I stand by my appreciation that the API design is focused on platform extensibility first, and it's not difficult to create a subclass of HttpMessageHandler (see mockhttp's as an example). It could even be a subclass that hands off the call to an abstract public method that is more Mock-friendly. I realise that seems "impure", but you could argue testibility vs tainting-API-surface-for-testing-purposes forever.

scott-martin commented 8 years ago

+1 for the interface.

Like others, I am a newcomer to HttpClient and need it in a class, started searching on how to unit test it, discovered this thread - and now several hours later I effectively have had to learn about it's implementation details in order to test it. The fact the so many have had to travel this road is proof that this is a real problem for people. And of the few that have spoken up, how many more are just reading the thread in silence?

I am in the middle of leading a large project at my company and simply do not have the time to chase down these sort of rabbit trails. An interface would have saved me time, my company money - and that is probably the same for many others.

I will probably go the route of creating my own wrapper. Because looking from the outside, it is not going to make sense to anyone why I injected an HttpMessageHandler into my class.

If it is not possible to create an interface for HttpClient - then perhaps the .net team can create a whole new http solution that does have an interface?

YehudahA commented 8 years ago

@scott-martin, HttpClient does nothing, he just sends the requests to HttpMessageHandler.

Instead of test the HttpClient you can test and mock the HttpMessageHandler. It's very simple, and you can also use richardszalay mockhttp.

richardszalay commented 8 years ago

@scott-martin as was mentioned only few comments up, you don't inject HttpMessageHandler. You inject HttpClient, and inject that with HttpMessageHandler when you are unit testing.

scott-martin commented 8 years ago

@richardszalay thanks for the tip, that does work better at keeping low-level abstractions out of my implementations.

@YehudahA, I was able to get it working by mocking the HttpMessageHandler. But "getting it working" is not why I chimed in. As @PureKrome said, this is a problem of discover-ability. If there was an interface, I would have been able to mock it and be on my way in a matter of minutes. Because there isn't one, I had to spend several hours tracking down a solution.

I understand the limitations and hesitations to providing an interface. I just want to give my feedback as a member of the community that we like interfaces and please give us more of them - they are much easier to interact with and test against.

PureKrome commented 8 years ago

this is a problem of discover-ability. If there was an interface, I would have been able to mock it and be on my way in a matter of minutes. Because there isn't one, I had to spend several hours tracking down a solution.

:arrow_up: this. It's all about, this.

EDIT: emphasis, mine.

richardszalay commented 8 years ago

While I completely agree that it could be more discoverable, I'm not sure how it could take "several hours". Googling "mock httpclient" brings up this Stack Overflow question as the first result, in which the two highest rated answers (though, granted, not the accepted answer) describe HttpMessageHandler.

PureKrome commented 8 years ago

NP - we still agree to disagree. Tis All good :)

luisrudge commented 8 years ago

you can close this issue, since mscorlib is coming back with all it's baggage :)

dasjestyr commented 8 years ago

Can we just have these httpclient classes implement an interface? Then can deal with the rest on our own.

colceagus commented 8 years ago

Which solution do you propose for mocking the HttpClient used in a DI Service ? I'm trying to implement a FakeHttpClientHandler and I don't know what to do with the SendAsync method.

I have a simple GeoLocationService that calls a microservice for retrieving ip based location information from another one. I want to pass the fake HttpClientHandler to the service, which has two constructors, one receiving the locationServiceAddress and one with the locationServiceAddress and the additional HttpClientHandler.

public class GeoLocationService: IGeoLocationService {

        private readonly string _geoLocationServiceAddress;
        private HttpClient _httpClient;
        private readonly object _gate = new object();

        /// <summary>
        /// Creates a GeoLocation Service Instance for Dependency Injection
        /// </summary>
        /// <param name="locationServiceAddress">The GeoLocation Service Hostname (IP Address), including port number</param>
        public GeoLocationService(string locationServiceAddress) {
            // Add the ending slash to be able to GetAsync with the ipAddress directly
            if (!locationServiceAddress.EndsWith("/")) {
                locationServiceAddress += "/";
            }

            this._geoLocationServiceAddress = locationServiceAddress;
            this._httpClient = new HttpClient {
                BaseAddress = new Uri(this._geoLocationServiceAddress)
            };
        }

        /// <summary>
        /// Creates a GeoLocation Service Instance for Dependency Injection (additional constructor for Unit Testing the Service)
        /// </summary>
        /// <param name="locationServiceAddress">The GeoLocation Service Hostname (IP Address), including port number.</param>
        /// <param name="clientHandler">The HttpClientHandler for the HttpClient for mocking responses in Unit Tests.</param>
        public GeoLocationService(string locationServiceAddress, HttpClientHandler clientHandler): this(locationServiceAddress) {
            this._httpClient.Dispose();
            this._httpClient = new HttpClient(clientHandler) {
                BaseAddress = new Uri(this._geoLocationServiceAddress)
            };
        }

        /// <summary>
        /// Geo Location Microservice Http Call with recreation of HttpClient in case of failure
        /// </summary>
        /// <param name="ipAddress">The ip address to locate geographically</param>
        /// <returns>A <see cref="string">string</see> representation of the Json Location Object.</returns>
        private async Task<string> CallGeoLocationServiceAsync(string ipAddress) {
            HttpResponseMessage response;
            string result = null;

            try {
                response = await _httpClient.GetAsync(ipAddress);

                response.EnsureSuccessStatusCode();

                result = await response.Content.ReadAsStringAsync();
            }
            catch (Exception ex) {
                lock (_gate) {
                    _httpClient.Dispose(); 
                    _httpClient = new HttpClient {
                        BaseAddress = new Uri(this._geoLocationServiceAddress)
                    };
                }

                result = null;

                Logger.LogExceptionLine("GeoLocationService", "CallGeoLocationServiceAsync", ex.Message, stacktrace: ex.StackTrace);
            }

            return result;
        }

        /// <summary>
        /// Calls the Geo Location Microservice and returns the location description for the provided IP Address. 
        /// </summary>
        /// <param name="ipAddress">The <see cref="string">IP Address</see> to be geographically located by the GeoLocation Microservice.</param>
        /// <returns>A <see cref="string">string</see> representing the json object location description. Does two retries in the case of call failure.</returns>
        public async Task<string> LocateAsync(string ipAddress) {
            int noOfRetries = 2;
            string result = "";

            for (int i = 0; i < noOfRetries; i ++) {
                result = await CallGeoLocationServiceAsync(ipAddress);

                if (result != null) {
                    return result;
                }
            }

            return String.Format(Constants.Location.DefaultGeoLocationResponse, ipAddress);
        }

        public void Dispose() {
            _httpClient.Dispose();
        }

    }

In the test class, I want to write an internal class named FakeClientHandler which extends HttpClientHandler, that overrides the SendAsync Method. Another method, would be, I guess, to use a mock framework to mock the HttpClientHandler. I don't know yet how to do that. If you could help, I would be forever in your debt.

public class GeoLocationServiceTests {

        private readonly IConfiguration _configuration;
        private IGeoLocationService _geoLocationService;
        private HttpClientHandler _clientHandler;

        internal class FakeClientHandler : HttpClientHandler {

            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {

                // insert implementation here

                // insert fake responses here. 
                return (Task<HttpResponseMessage>) null;
            }

        }

        public GeoLocationServiceTests() {
            this._clientHandler = new FakeClientHandler();
            this._clientHandler.UseDefaultCredentials = true;
            this._geoLocationService = new GeoLocationService("http://fakegeolocation.com/json/", this._clientHandler);
        }

        [Fact]
        public async void RequestGeoLocationFromMicroservice() {
            string ipAddress = "192.36.1.252";

            string apiResponse = await this._geoLocationService.LocateAsync(ipAddress);
            Dictionary<string, string> result = JsonConvert.DeserializeObject<Dictionary<string,string>>(apiResponse);

            Assert.True(result.ContainsKey("city"));

            string timeZone;
            result.TryGetValue("time_zone", out timeZone);

            Assert.Equal(timeZone, @"Europe/Stockholm");
        }

        [Fact]
        public async void RequestBadGeoLocation() {
            string badIpAddress = "asjldf";

            string apiResponse = await this._geoLocationService.LocateAsync(badIpAddress);
            Dictionary<string, string> result = JsonConvert.DeserializeObject<Dictionary<string, string>>(apiResponse);

            Assert.True(result.ContainsKey("city"));

            string city;
            result.TryGetValue("city", out city);
            Assert.Equal(city, "NA");

            string ipAddress;
            result.TryGetValue("ip", out ipAddress);
            Assert.Equal(badIpAddress, ipAddress);
        }
    }
richardszalay commented 8 years ago

@danielmihai The HttpMessageHandler is effectively the "implementation". You typically create an HttpClient, passing the handler into the constuctor, and then have your service layer depend on HttpClient.

Using a generic mocking framework is difficult because the overridable SendAsync is protected. I wrote mockhttp specifically for that reason (but also because it's nice to have domain-specific mocking features.

PureKrome commented 8 years ago

@danielmihai What Richard said pretty much sums up the pain we all have to endure right now with mocking HttpClient.

There's also HttpClient.Helpers as another library which helps you mock out HttpClient .. or more specifically, provide a fake HttpMessageHandler for you to fake up the responses.

colceagus commented 8 years ago

I've figured it out, written something like this... [I think I've dealt with the 'pain']

internal class FakeClientHandler : HttpClientHandler {
            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
                if (request.Method == HttpMethod.Get) {
                    if (request.RequestUri.PathAndQuery.Contains("/json/")) {
                        string requestPath = request.RequestUri.PathAndQuery;
                        string[] splitPath = requestPath.Split(new char[] { '/' });
                        string ipAddress = splitPath.Last();

                        // RequestGeoLocationFromMicroservice
                        if (ipAddress.Equals(ipAddress1)) {
                            var response = new HttpResponseMessage(HttpStatusCode.OK);
                            response.Content = new StringContent(response1);

                            return Task.FromResult(response);
                        }

                        // RequestBadGeoLocation
                        if (ipAddress.Equals(ipAddress2)) {
                            var response = new HttpResponseMessage(HttpStatusCode.OK);
                            response.Content = new StringContent(response2);
                            return Task.FromResult(response);
                        }
                    }
                }
                return (Task<HttpResponseMessage>) null;
            }
        }

All tests pass

Please do comment on the implementation, if there are better ways to do it.

Thanks!

Thaoden commented 8 years ago

Adding my thoughts to this more than a year old discussion.

@SidharthNabar

First, I notice that you are creating new instances of HttpClient for sending each request - that is not the intended design pattern for HttpClient. Creating one HttpClient instance and reusing it for all your requests helps optimize connection pooling and memory management. Please consider reusing a single instance of HttpClient. Once you do this, you can insert the fake handler into just one instance of HttpClient and you're done.

Where do I dispose of the not-injected instance when I'm not supposed to use a using statement, but new up my HttpClient instance eg in the consumer's constructor? What about the HttpMessageHandler that I'm constructing for injecting into the HttpClient instance?

The complete interface-or-not-discussion aside (I'm all for interfaces), at least the docs could state that you can inject a HttpMessageHandler for testing purposes. Yes, the constructor is mentioned, yes, it is mentioned that you can inject your own HttpMessageHandler, but nowhere is it stated why I would want to do this.

@richardszalay

While I completely agree that it could be more discoverable, I'm not sure how it could take "several hours". Googling "mock httpclient" brings up this Stack Overflow question as the first result, in which the two highest rated answers (though, granted, not the accepted answer) describe HttpMessageHandler.

An API should be intuitive to use with browsing through the docs, IMHO. Having to ask Google or SO question is certainly not intuitive, the linked question dating back to way before .NET Core makes the up-to-dateness not obvious (maybe something changed in between the question and the release of a new framework?)

Thanks to everyone who's added their code examples and provided helper libs!

dasjestyr commented 8 years ago

I mean, if we have a class that needs to make an http call, and we want to unit test that class, then we have to be able to mock that http client. Since http client isn't interfaced, I can't inject a mock for that client. Instead, I have to build a wrapper (that implements an interface) for http client that accepts a message handler as an optional parameter, and then send it a fake handler and inject that. That's a whole lot of extra hoops to jump through for nothing. The alternative is to have my class take an instance of HttpMessageHandler instead of HttpClient, I guess...

I don't mean to split hairs, it just feels awkward and seems a bit unintuitive. I think the fact that this is such a popular topic sort of supports that.

shaunluttin commented 7 years ago

Since http client isn't interfaced, we cant inject it with a container.

@dasjestyr We can do dependency injection without interfaces. Interfaces enable multiple inheritance; they are not a requirement for dependency injection.

PureKrome commented 7 years ago

Yeah, DI is not the issue here. It's how easy/nice it is to mock it out, etc.

dasjestyr commented 7 years ago

@shaunluttin I'm aware that we can do DI without an interface, I didn't imply otherwise; I'm speaking of a class that has a dependency on an HttpClient. Without the interface for the client, I can't inject a mocked http client or any http client (so that it can be tested in isolation without making an actual http call) unless I wrap it in an adapter that implements an interface that I've define to mimic that of the HttpClient. I've changed some wording slightly for clarity in case you were fixating on a single sentence.

In the meanwhile, I've been creating a fake message handler in my unit tests and injecting that which means my classes are designed around injecting a handler which I guess isn't the worst thing in the world but it feels awkward. And the tests need fakes instead of mocks/stubs. Gross.

Also, speaking academically, interfaces do not enable multiple inheritance, they only allow you to mimic it. You don't inherit interfaces, you implement them -- you're not inheriting any functionality by implementing interfaces, only declaring that you have implemented the functionality in some way. To otherwise mimic the multiple inheritance and some already existing functionality that you've written elsewhere, you might implement the interface and pipe the implementation to an internal member that holds the actual functionality (e.g. an adapter) which is actually composition, not inheritance.

PureKrome commented 7 years ago

In the meanwhile, I've been creating a fake message handler in my unit tests and injecting that which means my classes are designed around injecting a handler which I guess isn't the worst thing in the world but it feels awkward. And the tests need fakes instead of mocks/stubs. Gross.

This is heart/core of this issue -> and it's purely opinionated too.

One side of the fence: fake message handler because <insert their rational here> Other side of the fence: interfaces (or even virtual methods, but that still is a tiny bit of a PITA) because of <insert their rational here>.