Skip to content

Commit

Permalink
Add Request Header support to HttpOptions. (#17)
Browse files Browse the repository at this point in the history
Fixes #16
  • Loading branch information
PureKrome authored Feb 15, 2017
1 parent 1445772 commit 7f17b81
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 10 deletions.
9 changes: 9 additions & 0 deletions ReleaseNotes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Release Notes

### v5.1.0 (2017-02-06)
**Features**
- Added support for `Headers` in `HttpMessageOptions`.


### v1.0.0 -> v5.0.0
- Inital and subsequent releases in supporting faking an `HttpClient` request/response.
60 changes: 55 additions & 5 deletions src/HttpClient.Helpers/FakeMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public class FakeHttpMessageHandler : HttpClientHandler
/// </summary>
/// <remarks>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.</remarks>
public FakeHttpMessageHandler(HttpMessageOptions options) : this(new List<HttpMessageOptions>
{
options
})
{
options
})
{
}

Expand Down Expand Up @@ -61,7 +61,8 @@ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage reques
{
RequestUri = requestUri,
HttpMethod = request.Method,
HttpContent = request.Content
HttpContent = request.Content,
Headers = request.Headers.ToDictionary(kv => kv.Key, kv => kv.Value)
};

var expectedOption = GetExpectedOption(option);
Expand Down Expand Up @@ -134,7 +135,11 @@ private HttpMessageOptions GetExpectedOption(HttpMessageOptions option)
(x.HttpMethod == option.HttpMethod ||
x.HttpMethod == null) &&
(x.HttpContent == option.HttpContent ||
x.HttpContent == null));
x.HttpContent == null) &&
(x.Headers == null ||
x.Headers.Count == 0) ||
(x.Headers != null &&
HeaderExists(x.Headers, option.Headers)));
}

private static void IncrementCalls(HttpMessageOptions options)
Expand All @@ -154,5 +159,50 @@ private static void IncrementCalls(HttpMessageOptions options)
var existingValue = (int) propertyInfo.GetValue(options);
propertyInfo.SetValue(options, ++existingValue);
}

private static bool HeaderExists(IDictionary<string, IEnumerable<string>> source,
IDictionary<string, IEnumerable<string>> destination)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}

if (destination == null)
{
throw new ArgumentNullException(nameof(destination));
}

// Both sides are not the same size.
if (source.Count != destination.Count)
{
return false;
}

foreach (var key in source.Keys)
{
if (!destination.ContainsKey(key))
{
// Key is missing from the destination.
return false;
}

if (source[key].Count() != destination[key].Count())
{
// The destination now doesn't have the same size of 'values'.
return false;
}

foreach (var value in source[key])
{
if (!destination[key].Contains(value))
{
return false;
}
}
}

return true;
}
}
}
1 change: 1 addition & 0 deletions src/HttpClient.Helpers/HttpClient.Helpers.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<dependencies>
<dependency id="Microsoft.Net.Http" version="2.2.29" />
</dependencies>
<releaseNotes>Added support for Headers in HttpMessageOptions.</releaseNotes>
</metadata>
<files>
<file src="bin\Release\WorldDomination.HttpClient.Helpers.dll" target="lib\net45\WorldDomination.HttpClient.Helpers.dll" />
Expand Down
15 changes: 13 additions & 2 deletions src/HttpClient.Helpers/HttpMessageOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;

namespace WorldDomination.Net.Http
Expand Down Expand Up @@ -51,6 +53,11 @@ public HttpContent HttpContent
}
}

/// <summary>
/// Optional: If not provided, then assumed to have *no* headers.
/// </summary>
public IDictionary<string, IEnumerable<string>> Headers { get; set; }

// 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.
Expand All @@ -59,8 +66,12 @@ public HttpContent HttpContent
public override string ToString()
{
var httpMethodText = HttpMethod?.ToString() ?? NoValue;
return
$"{httpMethodText} {RequestUri}{(HttpContent != null ? $" body/content: {_httpContentSerialized}" : "")}";

var headers = Headers != null &&
Headers.Any()
? " " + string.Join(":", Headers.Select(x => $"{x.Key}|{string.Join(",", x.Value)}"))
: "";
return $"{httpMethodText} {RequestUri}{(HttpContent != null ? $" body/content: {_httpContentSerialized}" : "")}{headers}";
}
}
}
125 changes: 122 additions & 3 deletions tests/HttpClient.Helpers.Tests/GetAsyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,25 @@ public static IEnumerable<object[]> ValidHttpMessageOptions
HttpResponseMessage = SomeFakeResponse
}
};

// Has to match GET + URI + Header
yield return new object[]
{
new HttpMessageOptions
{
HttpMethod = HttpMethod.Get,
RequestUri = RequestUri,
Headers = new Dictionary<string, IEnumerable<string>>
{
{"Bearer", new[]
{
"pewpew"
}
}
},
HttpResponseMessage = SomeFakeResponse
}
};
}
}

Expand Down Expand Up @@ -118,17 +137,79 @@ public static IEnumerable<object[]> ValidSomeHttpMessageOptions
}
}


public static IEnumerable<object[]> DifferentHttpMessageOptions
{
get
{
yield return new object[]
{
// Different uri.
new HttpMessageOptions
{
RequestUri = "http://this.is.a.different.website"
}
};

yield return new object[]
{
// Different Method.
new HttpMessageOptions
{
HttpMethod = HttpMethod.Head
}
};

yield return new object[]
{
// Different header (different key).
new HttpMessageOptions
{
Headers = new Dictionary<string, IEnumerable<string>>
{
{
"xxxx", new[]
{
"pewpew"
}
}
}
}
};

yield return new object[]
{
// Different header (found key, different content).
new HttpMessageOptions
{
Headers = new Dictionary<string, IEnumerable<string>>
{
{
"Bearer", new[]
{
"pewpew"
}
}
}
}
};
}
}

[Theory]
[MemberData(nameof(ValidHttpMessageOptions))]
public async Task GivenAnHttpMessageOptions_GetAsync_ReturnsAFakeResponse(HttpMessageOptions options)
{
// Arrange.
var fakeHttpMessageHandler = new FakeHttpMessageHandler(options);

// Act & Assert.
// Act.
await DoGetAsync(RequestUri,
ExpectedContent,
fakeHttpMessageHandler);
fakeHttpMessageHandler,
options.Headers);

// Assert.
options.NumberOfTimesCalled.ShouldBe(1);
}

Expand Down Expand Up @@ -274,9 +355,37 @@ await DoGetAsync(RequestUri,
options.NumberOfTimesCalled.ShouldBe(3);
}

[Theory]
[MemberData(nameof(DifferentHttpMessageOptions))]
public async Task GivenSomeDifferentHttpMessageOptions_GetAsync_ShouldThrowAnException(HttpMessageOptions options)
{
// Arrange.
var fakeHttpMessageHandler = new FakeHttpMessageHandler(options);
var headers = new Dictionary<string, IEnumerable<string>>
{
{
"hi", new[]
{
"there"
}
}
};

// Act.
var exception = await Should.ThrowAsync<Exception>(() => DoGetAsync(RequestUri,
ExpectedContent,
fakeHttpMessageHandler,
headers));

// Assert.
exception.Message.ShouldStartWith("No HttpResponseMessage found for the Request Uri:");
options.NumberOfTimesCalled.ShouldBe(0);
}

private static async Task DoGetAsync(string requestUri,
string expectedResponseContent,
FakeHttpMessageHandler fakeHttpMessageHandler)
FakeHttpMessageHandler fakeHttpMessageHandler,
IDictionary<string, IEnumerable<string>> optionalHeaders =null)
{
requestUri.ShouldNotBeNullOrWhiteSpace();
expectedResponseContent.ShouldNotBeNullOrWhiteSpace();
Expand All @@ -286,6 +395,16 @@ private static async Task DoGetAsync(string requestUri,
string content;
using (var httpClient = new System.Net.Http.HttpClient(fakeHttpMessageHandler))
{
// Do we have any Headers?
if (optionalHeaders != null &&
optionalHeaders.Any())
{
foreach (var keyValue in optionalHeaders)
{
httpClient.DefaultRequestHeaders.Add(keyValue.Key, keyValue.Value);
}
}

// Act.
message = await httpClient.GetAsync(requestUri);
content = await message.Content.ReadAsStringAsync();
Expand Down

0 comments on commit 7f17b81

Please sign in to comment.