diff --git a/README.md b/README.md index f9b0abe..3441df6 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ CLI: `install-package WorldDomination.HttpClient.Helpers` ## Sample Code There's [plenty more examples](https://github.com/PureKrome/HttpClient.Helpers/wiki) about to wire up: -- [A really simple example](https://github.com/PureKrome/HttpClient.Helpers/wiki/Constructor-Injection) +- [A really simple example](https://github.com/PureKrome/HttpClient.Helpers/wiki/A-single-endpoint) - [Multiple endpoints](https://github.com/PureKrome/HttpClient.Helpers/wiki/Multiple-endpoints) at once - [Wildcard endpoints](https://github.com/PureKrome/HttpClient.Helpers/wiki/Wildcard-endpoints) - [Throwing exceptions](https://github.com/PureKrome/HttpClient.Helpers/wiki/Faking-an-Exception) and handling it diff --git a/src/HttpClient.Helpers/FakeMessageHandler.cs b/src/HttpClient.Helpers/FakeMessageHandler.cs index b412b02..be8196b 100644 --- a/src/HttpClient.Helpers/FakeMessageHandler.cs +++ b/src/HttpClient.Helpers/FakeMessageHandler.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -16,26 +15,10 @@ public class FakeHttpMessageHandler : HttpClientHandler private readonly IDictionary _lotsOfOptions = new Dictionary(); - /// /// A fake message handler. /// /// TIP: If you have a requestUri = "*", this is a catch-all ... so if none of the other requestUri's match, then it will fall back to this dictionary item. - ///// A dictionary of request endpoints and their respective fake response message. - //public FakeHttpMessageHandler(IDictionary httpResponseMessages) - //{ - // if (httpResponseMessages == null) - // { - // throw new ArgumentNullException(nameof(httpResponseMessages)); - // } - - // if (!httpResponseMessages.Any()) - // { - // throw new ArgumentOutOfRangeException(nameof(httpResponseMessages)); - // } - - // _responses = httpResponseMessages; - //} public FakeHttpMessageHandler(HttpMessageOptions options) : this(new List { options @@ -63,12 +46,6 @@ public FakeHttpMessageHandler(HttpRequestException exception) _exception = exception; } - /// - /// - /// - /// - /// - /// protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { diff --git a/src/HttpClient.Helpers/HttpMessageOptions.cs b/src/HttpClient.Helpers/HttpMessageOptions.cs index 853ef33..e7a4647 100644 --- a/src/HttpClient.Helpers/HttpMessageOptions.cs +++ b/src/HttpClient.Helpers/HttpMessageOptions.cs @@ -51,6 +51,9 @@ public HttpContent HttpContent } } + // Note: I'm using reflection to set the value in here because I want this value to be _read-only_. + // Secondly, this occurs during a UNIT TEST, so I consider the expensive reflection costs to be + // acceptable in this situation. public int NumberOfTimesCalled { get; private set; } public override string ToString() diff --git a/tests/HttpClient.Helpers.Tests/HttpClient.Helpers.Tests.csproj b/tests/HttpClient.Helpers.Tests/HttpClient.Helpers.Tests.csproj index 2c5f3a3..99506ca 100644 --- a/tests/HttpClient.Helpers.Tests/HttpClient.Helpers.Tests.csproj +++ b/tests/HttpClient.Helpers.Tests/HttpClient.Helpers.Tests.csproj @@ -32,6 +32,10 @@ 4 + + ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + ..\..\packages\Shouldly.2.8.2\lib\net40\Shouldly.dll True @@ -63,10 +67,15 @@ + + + + + diff --git a/tests/HttpClient.Helpers.Tests/Wiki Examples/Baa.cs b/tests/HttpClient.Helpers.Tests/Wiki Examples/Baa.cs new file mode 100644 index 0000000..e92fb6f --- /dev/null +++ b/tests/HttpClient.Helpers.Tests/Wiki Examples/Baa.cs @@ -0,0 +1,8 @@ +namespace WorldDomination.HttpClient.Helpers.Tests.Wiki_Examples +{ + public class Baa + { + public string FavGame { get; set; } + public string FavMovie { get; set; } + } +} \ No newline at end of file diff --git a/tests/HttpClient.Helpers.Tests/Wiki Examples/Foo.cs b/tests/HttpClient.Helpers.Tests/Wiki Examples/Foo.cs new file mode 100644 index 0000000..d9f3d36 --- /dev/null +++ b/tests/HttpClient.Helpers.Tests/Wiki Examples/Foo.cs @@ -0,0 +1,9 @@ +namespace WorldDomination.HttpClient.Helpers.Tests.Wiki_Examples +{ + public class Foo + { + public int Id { get; set; } + public string Name { get; set; } + public Baa Baa { get; set; } + } +} \ No newline at end of file diff --git a/tests/HttpClient.Helpers.Tests/Wiki Examples/MultipleEndPointTests.cs b/tests/HttpClient.Helpers.Tests/Wiki Examples/MultipleEndPointTests.cs new file mode 100644 index 0000000..5ddd152 --- /dev/null +++ b/tests/HttpClient.Helpers.Tests/Wiki Examples/MultipleEndPointTests.cs @@ -0,0 +1,54 @@ +using System.Threading.Tasks; +using Shouldly; +using WorldDomination.Net.Http; +using Xunit; + +namespace WorldDomination.HttpClient.Helpers.Tests.Wiki_Examples +{ + public class MultipleEndpointTests + { + [Fact] + public async Task GivenSomeValidHttpRequests_GetSomeDataAsync_ReturnsAFoo() + { + // Arrange. + + // 1. First fake response. + const string responseData1 = "{ \"Id\":69, \"Name\":\"Jane\" }"; + var messageResponse1 = FakeHttpMessageHandler.GetStringHttpResponseMessage(responseData1); + + // 2. Second fake response. + const string responseData2 = "{ \"FavGame\":\"Star Wars\", \"FavMovie\":\"Star Wars - all of em\" }"; + var messageResponse2 = FakeHttpMessageHandler.GetStringHttpResponseMessage(responseData2); + + // Prepare our 'options' with all of the above fake stuff. + var options = new[] + { + new HttpMessageOptions + { + RequestUri = MyService.GetFooEndPoint, + HttpResponseMessage = messageResponse1 + }, + new HttpMessageOptions + { + RequestUri = MyService.GetBaaEndPoint, + HttpResponseMessage = messageResponse2 + } + }; + + // 3. Use the fake responses if those urls are attempted. + var messageHandler = new FakeHttpMessageHandler(options); + + var myService = new MyService(messageHandler); + + // Act. + // NOTE: network traffic will not leave your computer because you've faked the response, above. + var result = await myService.GetAllDataAsync(); + + // Assert. + result.Id.ShouldBe(69); // Returned from GetSomeFooDataAsync. + result.Baa.FavMovie.ShouldBe("Star Wars - all of em"); // Returned from GetSomeBaaDataAsync. + options[0].NumberOfTimesCalled.ShouldBe(1); + options[1].NumberOfTimesCalled.ShouldBe(1); + } + } +} diff --git a/tests/HttpClient.Helpers.Tests/Wiki Examples/MyService.cs b/tests/HttpClient.Helpers.Tests/Wiki Examples/MyService.cs new file mode 100644 index 0000000..1a340ba --- /dev/null +++ b/tests/HttpClient.Helpers.Tests/Wiki Examples/MyService.cs @@ -0,0 +1,65 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Shouldly; +using WorldDomination.Net.Http; + +namespace WorldDomination.HttpClient.Helpers.Tests.Wiki_Examples +{ + public class MyService + { + public const string GetFooEndPoint = "http://www.something.com/some/website"; + public const string GetBaaEndPoint = "http://www.something.com/another/site"; + private readonly FakeHttpMessageHandler _messageHandler; + + public MyService(FakeHttpMessageHandler messageHandler = null) + { + _messageHandler = messageHandler; + } + + public async Task GetAllDataAsync() + { + var foo = await GetSomeFooDataAsync(); + foo.Baa = await GetSomeBaaDataAsync(); + + return foo; + } + + public async Task GetSomeFooDataAsync() + { + return await GetSomeDataAsync(GetFooEndPoint); + } + + public async Task GetSomeBaaDataAsync() + { + // NOTE: notice how this request endpoint is different to the one, above? + return await GetSomeDataAsync(GetBaaEndPoint); + } + + private async Task GetSomeDataAsync(string endPoint) + { + endPoint.ShouldNotBeNullOrWhiteSpace(); + + HttpResponseMessage message; + string content; + using (var httpClient = _messageHandler == null + ? new System.Net.Http.HttpClient() + : new System.Net.Http.HttpClient(_messageHandler)) + { + message = await httpClient.GetAsync(endPoint); + content = await message.Content.ReadAsStringAsync(); + } + + if (message.StatusCode != HttpStatusCode.OK) + { + // TODO: handle this ru-roh-error. + throw new InvalidOperationException(content); + } + + // Assumption: content is in a json format. + return JsonConvert.DeserializeObject(content); + } + } +} \ No newline at end of file diff --git a/tests/HttpClient.Helpers.Tests/Wiki Examples/SingleEndpointTests.cs b/tests/HttpClient.Helpers.Tests/Wiki Examples/SingleEndpointTests.cs new file mode 100644 index 0000000..b85a4e5 --- /dev/null +++ b/tests/HttpClient.Helpers.Tests/Wiki Examples/SingleEndpointTests.cs @@ -0,0 +1,90 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using Shouldly; +using WorldDomination.Net.Http; +using Xunit; + +namespace WorldDomination.HttpClient.Helpers.Tests.Wiki_Examples +{ + public class SingleEndpointTests + { + [Theory] + [InlineData(MyService.GetFooEndPoint)] // Specific url they are hitting. + [InlineData("*")] // Don't care what url they are hitting. + public async Task GivenSomeValidHttpRequest_GetSomeFooDataAsync_ReturnsAFoo(string endPoint) + { + // Arrange. + const string responseData = "{ \"Id\":69, \"Name\":\"Jane\" }"; + var messageResponse = FakeHttpMessageHandler.GetStringHttpResponseMessage(responseData); + + var options = new HttpMessageOptions + { + RequestUri = endPoint, + HttpResponseMessage = messageResponse + }; + + var messageHandler = new FakeHttpMessageHandler(options); + + var myService = new MyService(messageHandler); + + // Act. + // NOTE: network traffic will not leave your computer because you've faked the response, above. + var result = await myService.GetSomeFooDataAsync(); + + // Assert. + result.Id.ShouldBe(69); // Returned from GetSomeFooDataAsync. + result.Baa.ShouldBeNull(); + options.NumberOfTimesCalled.ShouldBe(1); + } + + [Fact] + public async Task GivenADifferentFakeUrlEndpoint_GetSomeFooDataAsync_ThrowsAnException() + { + // Arrange. + const string responseData = "{ \"Id\":69, \"Name\":\"Jane\" }"; + var messageResponse = FakeHttpMessageHandler.GetStringHttpResponseMessage(responseData); + + var options = new HttpMessageOptions + { + RequestUri = "http://this.is.not.the.correct.endpoint", + HttpResponseMessage = messageResponse + }; + + var messageHandler = new FakeHttpMessageHandler(options); + + var myService = new MyService(messageHandler); + + // Act. + // NOTE: network traffic will not leave your computer because you've faked the response, above. + var result = await Should.ThrowAsync(myService.GetSomeFooDataAsync()); + + // Assert. + result.Message.ShouldStartWith("No HttpResponseMessage found"); + } + + [Fact] + public async Task GivenAServerError_GetSomeFooDataAsync_ThrowsAnException() + { + // Arrange. + const string responseData = "Something Blew Up"; + var messageResponse = FakeHttpMessageHandler.GetStringHttpResponseMessage(responseData, + HttpStatusCode.InternalServerError); + + var options = new HttpMessageOptions + { + HttpResponseMessage = messageResponse + }; + var messageHandler = new FakeHttpMessageHandler(options); + + var myService = new MyService(messageHandler); + + // Act. + // NOTE: network traffic will not leave your computer because you've faked the response, above. + var result = await Should.ThrowAsync(myService.GetSomeFooDataAsync()); + + // Assert. + result.Message.ShouldStartWith(responseData); + } + } +} \ No newline at end of file diff --git a/tests/HttpClient.Helpers.Tests/packages.config b/tests/HttpClient.Helpers.Tests/packages.config index 2fd4dc8..80e8fc6 100644 --- a/tests/HttpClient.Helpers.Tests/packages.config +++ b/tests/HttpClient.Helpers.Tests/packages.config @@ -3,6 +3,7 @@ +