Skip to content

Commit c946a4d

Browse files
authored
Merge pull request #24 from ppittle/http-client-documentation
Http client documentation
2 parents c4f1a32 + ce5108c commit c946a4d

File tree

2 files changed

+87
-52
lines changed

2 files changed

+87
-52
lines changed

README.md

Lines changed: 85 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
# HttpWebRequestWrapper
88

9-
**HttpWebRequestWrapper** is a testing layer for Microsoft's `HttpWebRequest` and `WebClient` classes. It overcomes restrictions that would normally prevent mocking a `HttpWebRequest` and allows testing your application with faked HTTP requests in Unit and BDD tests.
9+
**HttpWebRequestWrapper** is a testing layer for Microsoft's `HttpClient`, `HttpWebRequest` and `WebClient` classes. It overcomes restrictions that would normally prevent mocking a `HttpWebRequest` and allows testing your application with faked HTTP requests in Unit and BDD tests.
1010

11-
Ideal for testing application code that relies on http api calls either directly or through 3rd party libraries!
11+
`HttpWebRequestWrapper` is built with some serious secret sauce allowing you to intercept nearlly all http traffic, including calls made in 3rd party code and code that doesn't support dependency injection! It's the ideal testing tool for testing application code that relies on http api calls either directly or through 3rd party libraries!
1212

1313
## NuGet
1414

@@ -18,6 +18,29 @@ HttpWebRequestWrapper has no 3rd Party dependencies!
1818

1919
## Usage
2020

21+
```csharp
22+
// Asserts we can use a HttpWebRequestWrapperSession to
23+
// intercept and replace the Response to
24+
// new HttpClient().GetStringAsync("https://www.github.com")
25+
[Test]
26+
public async Task InterceptAndReplaceTrafficToGitHub()
27+
{
28+
var fakeResponse = "Testing";
29+
30+
var interceptor =
31+
new HttpWebRequestWrapperInterceptorCreator(x =>
32+
x.HttpWebResponseCreator.Create(fakeResponse));
33+
34+
using (new HttpWebRequestWrapperSession(interceptor))
35+
{
36+
var responseBody = await new HttpClient().GetStringAsync("https://www.github.com");
37+
38+
Assert.Equal(fakeResponse, responseBody);
39+
}
40+
}
41+
```
42+
43+
### Testing Application Code
2144
Let's say you have some simple but very hard to test code that uses `WebRequest.Create` to make a live http call:
2245

2346
```csharp
@@ -37,15 +60,16 @@ public static class Example
3760
}
3861
```
3962

40-
41-
Easily unit test your code with the **HttpWebRequestWrapper** Library:
63+
It would be ideal if this code used seperation of concerns and dependency injection to be more testable. But perhaps it's in a 3rd party library, or it'll be too expensive to refacotr.
64+
Fortunatly, it can b easily unit tested with the **HttpWebRequestWrapper** Library! The test below intercepts the http call made by `Example.CountCharactersOnAWebPage` and returns a fake html response:
4265

4366
```csharp
4467
// ARRANGE
4568
var fakeResponseBody = "<html>Test</html>";
4669

4770
using (new HttpWebRequestWrapperSession(
48-
new HttpWebRequestWrapperInterceptorCreator(req => req.HttpWebResponseCreator.Create(fakeResponseBody))))
71+
new HttpWebRequestWrapperInterceptorCreator(req =>
72+
req.HttpWebResponseCreator.Create(fakeResponseBody))))
4973
{
5074
// ACT
5175
var charactersOnGitHub = Example.CountCharactersOnAWebPage("http://www.github.com");
@@ -55,46 +79,9 @@ using (new HttpWebRequestWrapperSession(
5579
}
5680
```
5781

58-
### Full Mocking
82+
### HttpClient, WebClient, and WebRequest.Create() Support
5983

60-
Go crazy with full mocking support! The ` HttpWebRequestWrapperInterceptor` provides very powerful faking, but you can easily build your own mock `HttpWebRequestWrapper` to provide custom behavior or expectations.
61-
62-
```csharp
63-
// ARRANGE
64-
var fakeResponseBody = "Fake Response";
65-
var mockWebRequest = new Mock<HttpWebRequestWrapper>(new Uri("http://www.github.com"));
66-
mockWebRequest
67-
.Setup(x => x.GetResponse())
68-
.Returns(HttpWebResponseCreator.Create(
69-
new Uri("http://www.github.com"),
70-
"GET",
71-
HttpStatusCode.OK,
72-
"Fake Response"));
73-
74-
var mockCreator = new Mock<IWebRequestCreate>();
75-
mockCreator
76-
.Setup(x => x.Create(It.IsAny<Uri>()))
77-
.Returns(mockWebRequest.Object);
78-
79-
// ACT
80-
string responseBody;
81-
using (new HttpWebRequestWrapperSession(mockCreator.Object))
82-
{
83-
request = (HttpWebRequest)WebRequest.Create("http://www.github.com");
84-
85-
using (var sr = new StreamReader(request.GetResponse().GetResponseStream()))
86-
responseBody = sr.ReadToEnd();
87-
}
88-
89-
// ASSERT
90-
Assert.Equal(fakeResponseBody, responseBody);
91-
mockWebRequest.Verify(x => x.GetResponse());
92-
93-
```
94-
95-
### WebClient Support
96-
97-
**HttpWebRequestWrapper** also fully supports the .net `WebClient` class:
84+
**HttpWebRequestWrapper** fully supports the .net `HttpClient` and `WebClient` classes as well as the static `WebRequest.Create()` method:
9885

9986
```csharp
10087
var fakeResponse = "Testing";
@@ -103,9 +90,16 @@ using (new HttpWebRequestWrapperSession(
10390
new HttpWebRequestWrapperInterceptorCreator(
10491
x => x.HttpWebResponseCreator.Create(fakeResponse))))
10592
{
106-
var responseBody = new WebClient().DownloadString("https://www.github.com");
93+
var responseBody1 = await new HttpClient().GetStringAsync("https://www.github.com");
94+
var responseBody2 = new WebClient().DownloadString("https://www.github.com");
95+
96+
var response = WebRequest.Create("https://www.github.com").GetResponse();
10797

108-
Assert.Equal(fakeResponse, responseBody);
98+
Assert.Equal(fakeResponse, responseBody1);
99+
Assert.Equal(fakeResponse, responseBody2);
100+
101+
using (var sr = new StreamReader(response.GetResponseStream())
102+
Assert.Equal(fakeResponse, sr.ReadToEnd());
109103
}
110104
```
111105

@@ -270,6 +264,43 @@ using (new HttpWebRequestWrapperSession(new HttpWebRequestWrapperDelegateCreator
270264
}
271265
```
272266

267+
### Full Mocking
268+
269+
Go crazy with full mocking support! The ` HttpWebRequestWrapperInterceptor` provides very powerful faking, but you can easily build your own mock `HttpWebRequestWrapper` to provide custom behavior or expectations.
270+
271+
```csharp
272+
// ARRANGE
273+
var fakeResponseBody = "Fake Response";
274+
var mockWebRequest = new Mock<HttpWebRequestWrapper>(new Uri("http://www.github.com"));
275+
mockWebRequest
276+
.Setup(x => x.GetResponse())
277+
.Returns(HttpWebResponseCreator.Create(
278+
new Uri("http://www.github.com"),
279+
"GET",
280+
HttpStatusCode.OK,
281+
"Fake Response"));
282+
283+
var mockCreator = new Mock<IWebRequestCreate>();
284+
mockCreator
285+
.Setup(x => x.Create(It.IsAny<Uri>()))
286+
.Returns(mockWebRequest.Object);
287+
288+
// ACT
289+
string responseBody;
290+
using (new HttpWebRequestWrapperSession(mockCreator.Object))
291+
{
292+
request = (HttpWebRequest)WebRequest.Create("http://www.github.com");
293+
294+
using (var sr = new StreamReader(request.GetResponse().GetResponseStream()))
295+
responseBody = sr.ReadToEnd();
296+
}
297+
298+
// ASSERT
299+
Assert.Equal(fakeResponseBody, responseBody);
300+
mockWebRequest.Verify(x => x.GetResponse());
301+
302+
```
303+
273304
## Secret Sauce
274305

275306
**HttpWebRequestWrapper** works by inheriting from `HttpWebRequest`. This doesn't seem revolutionary, except these are the `HttpWebRequest` constructors:
@@ -296,22 +327,25 @@ The second piece of magic is hooking into `WebRequest.PrefixList`. `WebRequest`
296327

297328
Disposing of the Session restores the original `Prefix` list.
298329

299-
### Limitations
330+
### HttpClient
300331

301-
**HttpWebRequestWrapper** can *not* support concurrent test execution. Becuase `HttpWebRequestWrapperSession` works by setting a global static variable it's not possible to have two Sessions in use at one time. Code inside the Session's using block is free to execute concurrently, you just can't try and use two or more Sessions at once.
332+
`HttpClient` uses an internal `HttpWebRequest` constructor to directly create requests and by-passes `WebRequest.PrefixList`. So instead,
333+
**HttpWebRequestWrapper** uses a custom `TaskScheduler` to hook into `HttpClientHandler` and hijack the `HttpClientHandler.StartRequeset` method to replace the underlying `HttpWebRequest` it will use with one created via `WebRequest.Create`.
302334

303-
### HttpClient
335+
### Limitations
304336

305-
`HttpClient` is not supported by **HttpWebRequestWrapper** because while it does use `HttpWebRequest` under the hood, it does not honor the `WebRequest.PrefixList` for determining which `IWebRequestCreate` to use to create the `HttpWebRequest` it uses. So `HttpWebRequestWrapperSession` can't hook into it.
337+
**HttpWebRequestWrapper** can *not* support concurrent test execution. Becuase `HttpWebRequestWrapperSession` works by setting a global static variable it's not possible to have two Sessions in use at one time. Code inside the Session's using block is free to execute concurrently, you just can't try and use two or more Sessions at once.
306338

307-
If you're looking for testing tools specifically for `HttpClient`, try [MockHttp](https://github.com/richardszalay/mockhttp).
339+
`HttpClient` performs IO via the abstract `HttpMessageHandler`. By default, this is a `HttpClinetHandler`. **HttpWebRequestWrapper** fully supports `HttpClientHandler` and any handler that inherits from `HttpClientHandler`. Custom handlers that instead inheirt directly from `HttpMessageHandler` are not supported and will not be intercepted.
308340

309341
## Platform Support
310342

311343
The actual wrapper `HttpWebRequestWrapper` is compiled for .NET Framework 2.0 and supports up to versions 4.7 of the .NET Framework (newest version tested at the time).
312344

313345
The remainder of **HttpWebRequestWrapper** requires .NET Framework 3.5+
314346

347+
Support for `HttpClient` requires .NET Framework 4.5+
348+
315349
## Build
316350

317351
Clone the repository and build `/src/HttpWebRequestWrapper.sln` using Visual Studio. NuGet package restore must be enabled.

src/HttpWebRequestWrapper/HttpWebRequestWrapper.nuspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
<requireLicenseAcceptance>false</requireLicenseAcceptance>
1212
<description>Test/mock (3rd party) code reliant on HttpClient, WebClient, HttpWebRequest and WebRequest.Create() </description>
1313
<summary>Test/mock (3rd party) code reliant on HttpClient, WebClient, HttpWebRequest and WebRequest.Create()</summary>
14-
<releaseNotes>Test/mock (3rd party) code reliant on HttpWebRequest and WebRequest.Create()!
14+
<releaseNotes>
15+
Test/mock (3rd party) code reliant on HttpClient, HttpWebRequest, WebClient and WebRequest.Create()!
1516

1617
Includes mockable MockHttpWebRequest, HttpWebRequestWrapperSession, HttpWebRequestWrapperRecorder, HttpWebRequestWrapperInterceptor and Playback controls
1718

0 commit comments

Comments
 (0)