Skip to content

Commit 7f17b81

Browse files
authored
Add Request Header support to HttpOptions. (#17)
Fixes #16
1 parent 1445772 commit 7f17b81

File tree

5 files changed

+200
-10
lines changed

5 files changed

+200
-10
lines changed

ReleaseNotes.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Release Notes
2+
3+
### v5.1.0 (2017-02-06)
4+
**Features**
5+
- Added support for `Headers` in `HttpMessageOptions`.
6+
7+
8+
### v1.0.0 -> v5.0.0
9+
- Inital and subsequent releases in supporting faking an `HttpClient` request/response.

src/HttpClient.Helpers/FakeMessageHandler.cs

+55-5
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ public class FakeHttpMessageHandler : HttpClientHandler
2020
/// </summary>
2121
/// <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>
2222
public FakeHttpMessageHandler(HttpMessageOptions options) : this(new List<HttpMessageOptions>
23-
{
24-
options
25-
})
23+
{
24+
options
25+
})
2626
{
2727
}
2828

@@ -61,7 +61,8 @@ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage reques
6161
{
6262
RequestUri = requestUri,
6363
HttpMethod = request.Method,
64-
HttpContent = request.Content
64+
HttpContent = request.Content,
65+
Headers = request.Headers.ToDictionary(kv => kv.Key, kv => kv.Value)
6566
};
6667

6768
var expectedOption = GetExpectedOption(option);
@@ -134,7 +135,11 @@ private HttpMessageOptions GetExpectedOption(HttpMessageOptions option)
134135
(x.HttpMethod == option.HttpMethod ||
135136
x.HttpMethod == null) &&
136137
(x.HttpContent == option.HttpContent ||
137-
x.HttpContent == null));
138+
x.HttpContent == null) &&
139+
(x.Headers == null ||
140+
x.Headers.Count == 0) ||
141+
(x.Headers != null &&
142+
HeaderExists(x.Headers, option.Headers)));
138143
}
139144

140145
private static void IncrementCalls(HttpMessageOptions options)
@@ -154,5 +159,50 @@ private static void IncrementCalls(HttpMessageOptions options)
154159
var existingValue = (int) propertyInfo.GetValue(options);
155160
propertyInfo.SetValue(options, ++existingValue);
156161
}
162+
163+
private static bool HeaderExists(IDictionary<string, IEnumerable<string>> source,
164+
IDictionary<string, IEnumerable<string>> destination)
165+
{
166+
if (source == null)
167+
{
168+
throw new ArgumentNullException(nameof(source));
169+
}
170+
171+
if (destination == null)
172+
{
173+
throw new ArgumentNullException(nameof(destination));
174+
}
175+
176+
// Both sides are not the same size.
177+
if (source.Count != destination.Count)
178+
{
179+
return false;
180+
}
181+
182+
foreach (var key in source.Keys)
183+
{
184+
if (!destination.ContainsKey(key))
185+
{
186+
// Key is missing from the destination.
187+
return false;
188+
}
189+
190+
if (source[key].Count() != destination[key].Count())
191+
{
192+
// The destination now doesn't have the same size of 'values'.
193+
return false;
194+
}
195+
196+
foreach (var value in source[key])
197+
{
198+
if (!destination[key].Contains(value))
199+
{
200+
return false;
201+
}
202+
}
203+
}
204+
205+
return true;
206+
}
157207
}
158208
}

src/HttpClient.Helpers/HttpClient.Helpers.nuspec

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<dependencies>
1919
<dependency id="Microsoft.Net.Http" version="2.2.29" />
2020
</dependencies>
21+
<releaseNotes>Added support for Headers in HttpMessageOptions.</releaseNotes>
2122
</metadata>
2223
<files>
2324
<file src="bin\Release\WorldDomination.HttpClient.Helpers.dll" target="lib\net45\WorldDomination.HttpClient.Helpers.dll" />

src/HttpClient.Helpers/HttpMessageOptions.cs

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
24
using System.Net.Http;
35

46
namespace WorldDomination.Net.Http
@@ -51,6 +53,11 @@ public HttpContent HttpContent
5153
}
5254
}
5355

56+
/// <summary>
57+
/// Optional: If not provided, then assumed to have *no* headers.
58+
/// </summary>
59+
public IDictionary<string, IEnumerable<string>> Headers { get; set; }
60+
5461
// Note: I'm using reflection to set the value in here because I want this value to be _read-only_.
5562
// Secondly, this occurs during a UNIT TEST, so I consider the expensive reflection costs to be
5663
// acceptable in this situation.
@@ -59,8 +66,12 @@ public HttpContent HttpContent
5966
public override string ToString()
6067
{
6168
var httpMethodText = HttpMethod?.ToString() ?? NoValue;
62-
return
63-
$"{httpMethodText} {RequestUri}{(HttpContent != null ? $" body/content: {_httpContentSerialized}" : "")}";
69+
70+
var headers = Headers != null &&
71+
Headers.Any()
72+
? " " + string.Join(":", Headers.Select(x => $"{x.Key}|{string.Join(",", x.Value)}"))
73+
: "";
74+
return $"{httpMethodText} {RequestUri}{(HttpContent != null ? $" body/content: {_httpContentSerialized}" : "")}{headers}";
6475
}
6576
}
6677
}

tests/HttpClient.Helpers.Tests/GetAsyncTests.cs

+122-3
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,25 @@ public static IEnumerable<object[]> ValidHttpMessageOptions
7676
HttpResponseMessage = SomeFakeResponse
7777
}
7878
};
79+
80+
// Has to match GET + URI + Header
81+
yield return new object[]
82+
{
83+
new HttpMessageOptions
84+
{
85+
HttpMethod = HttpMethod.Get,
86+
RequestUri = RequestUri,
87+
Headers = new Dictionary<string, IEnumerable<string>>
88+
{
89+
{"Bearer", new[]
90+
{
91+
"pewpew"
92+
}
93+
}
94+
},
95+
HttpResponseMessage = SomeFakeResponse
96+
}
97+
};
7998
}
8099
}
81100

@@ -118,17 +137,79 @@ public static IEnumerable<object[]> ValidSomeHttpMessageOptions
118137
}
119138
}
120139

140+
141+
public static IEnumerable<object[]> DifferentHttpMessageOptions
142+
{
143+
get
144+
{
145+
yield return new object[]
146+
{
147+
// Different uri.
148+
new HttpMessageOptions
149+
{
150+
RequestUri = "http://this.is.a.different.website"
151+
}
152+
};
153+
154+
yield return new object[]
155+
{
156+
// Different Method.
157+
new HttpMessageOptions
158+
{
159+
HttpMethod = HttpMethod.Head
160+
}
161+
};
162+
163+
yield return new object[]
164+
{
165+
// Different header (different key).
166+
new HttpMessageOptions
167+
{
168+
Headers = new Dictionary<string, IEnumerable<string>>
169+
{
170+
{
171+
"xxxx", new[]
172+
{
173+
"pewpew"
174+
}
175+
}
176+
}
177+
}
178+
};
179+
180+
yield return new object[]
181+
{
182+
// Different header (found key, different content).
183+
new HttpMessageOptions
184+
{
185+
Headers = new Dictionary<string, IEnumerable<string>>
186+
{
187+
{
188+
"Bearer", new[]
189+
{
190+
"pewpew"
191+
}
192+
}
193+
}
194+
}
195+
};
196+
}
197+
}
198+
121199
[Theory]
122200
[MemberData(nameof(ValidHttpMessageOptions))]
123201
public async Task GivenAnHttpMessageOptions_GetAsync_ReturnsAFakeResponse(HttpMessageOptions options)
124202
{
125203
// Arrange.
126204
var fakeHttpMessageHandler = new FakeHttpMessageHandler(options);
127205

128-
// Act & Assert.
206+
// Act.
129207
await DoGetAsync(RequestUri,
130208
ExpectedContent,
131-
fakeHttpMessageHandler);
209+
fakeHttpMessageHandler,
210+
options.Headers);
211+
212+
// Assert.
132213
options.NumberOfTimesCalled.ShouldBe(1);
133214
}
134215

@@ -274,9 +355,37 @@ await DoGetAsync(RequestUri,
274355
options.NumberOfTimesCalled.ShouldBe(3);
275356
}
276357

358+
[Theory]
359+
[MemberData(nameof(DifferentHttpMessageOptions))]
360+
public async Task GivenSomeDifferentHttpMessageOptions_GetAsync_ShouldThrowAnException(HttpMessageOptions options)
361+
{
362+
// Arrange.
363+
var fakeHttpMessageHandler = new FakeHttpMessageHandler(options);
364+
var headers = new Dictionary<string, IEnumerable<string>>
365+
{
366+
{
367+
"hi", new[]
368+
{
369+
"there"
370+
}
371+
}
372+
};
373+
374+
// Act.
375+
var exception = await Should.ThrowAsync<Exception>(() => DoGetAsync(RequestUri,
376+
ExpectedContent,
377+
fakeHttpMessageHandler,
378+
headers));
379+
380+
// Assert.
381+
exception.Message.ShouldStartWith("No HttpResponseMessage found for the Request Uri:");
382+
options.NumberOfTimesCalled.ShouldBe(0);
383+
}
384+
277385
private static async Task DoGetAsync(string requestUri,
278386
string expectedResponseContent,
279-
FakeHttpMessageHandler fakeHttpMessageHandler)
387+
FakeHttpMessageHandler fakeHttpMessageHandler,
388+
IDictionary<string, IEnumerable<string>> optionalHeaders =null)
280389
{
281390
requestUri.ShouldNotBeNullOrWhiteSpace();
282391
expectedResponseContent.ShouldNotBeNullOrWhiteSpace();
@@ -286,6 +395,16 @@ private static async Task DoGetAsync(string requestUri,
286395
string content;
287396
using (var httpClient = new System.Net.Http.HttpClient(fakeHttpMessageHandler))
288397
{
398+
// Do we have any Headers?
399+
if (optionalHeaders != null &&
400+
optionalHeaders.Any())
401+
{
402+
foreach (var keyValue in optionalHeaders)
403+
{
404+
httpClient.DefaultRequestHeaders.Add(keyValue.Key, keyValue.Value);
405+
}
406+
}
407+
289408
// Act.
290409
message = await httpClient.GetAsync(requestUri);
291410
content = await message.Content.ReadAsStringAsync();

0 commit comments

Comments
 (0)