Skip to content

Add Request Header support to HttpOptions. #17

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

Merged
merged 8 commits into from
Feb 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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