Skip to content

Commit feaccc2

Browse files
authored
Adds HttpResponseMessageBuilder for easy response generation
Creating responses can now be done using: ``` sut.WithResponse(response => response.WithJsonContent(new { Status=200, Reason="OK" })); ``` Closes #4
1 parent e7bc345 commit feaccc2

File tree

6 files changed

+468
-19
lines changed

6 files changed

+468
-19
lines changed

src/HttpClientTestHelpers/HttpClientTestHelpers.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<PrivateAssets>all</PrivateAssets>
2323
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2424
</PackageReference>
25+
<PackageReference Include="System.Text.Json" Version="4.7.1" />
2526
</ItemGroup>
2627

2728
<ItemGroup>
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Net;
4+
using System.Net.Http;
5+
using System.Net.Http.Headers;
6+
using System.Text;
7+
using System.Text.Json;
8+
9+
namespace HttpClientTestHelpers
10+
{
11+
/// <summary>
12+
/// This class helps creating an <see cref="HttpResponseMessage"/> using a fluent interface.
13+
/// </summary>
14+
[SuppressMessage("Design", "CA1001:Types that own disposable fields should be disposable", Justification = "The HttpResponseMessage is only created and passed to the consumer.")]
15+
public sealed class HttpResponseMessageBuilder
16+
{
17+
private readonly HttpResponseMessage httpResponseMessage = new HttpResponseMessage
18+
{
19+
Content = new StringContent("")
20+
};
21+
22+
/// <summary>
23+
/// Specifies the version of the response.
24+
/// </summary>
25+
/// <param name="httpVersion">The <see cref="HttpVersion"/> of the response.</param>
26+
/// <returns>The <see cref="HttpResponseMessageBuilder"/> for further building of the response.</returns>
27+
public HttpResponseMessageBuilder WithVersion(Version httpVersion)
28+
{
29+
httpResponseMessage.Version = httpVersion;
30+
return this;
31+
}
32+
33+
/// <summary>
34+
/// Specifies the status code of the response.
35+
/// </summary>
36+
/// <param name="statusCode">The <see cref="HttpStatusCode"/> of the response.</param>
37+
/// <returns>The <see cref="HttpResponseMessageBuilder"/> for further building of the response.</returns>
38+
public HttpResponseMessageBuilder WithStatusCode(HttpStatusCode statusCode)
39+
{
40+
httpResponseMessage.StatusCode = statusCode;
41+
return this;
42+
}
43+
44+
/// <summary>
45+
/// Configure request headers using a builder by directly accessing the <see cref="HttpResponseHeaders"/>.
46+
/// </summary>
47+
/// <param name="responseHeaderBuilder">The builder for configuring the response headers.</param>
48+
/// <returns>The <see cref="HttpResponseMessageBuilder"/> for further building of the response.</returns>
49+
public HttpResponseMessageBuilder WithHeaders(Action<HttpResponseHeaders> responseHeaderBuilder)
50+
{
51+
if (responseHeaderBuilder == null)
52+
{
53+
throw new ArgumentNullException(nameof(responseHeaderBuilder));
54+
}
55+
56+
responseHeaderBuilder(httpResponseMessage.Headers);
57+
return this;
58+
}
59+
60+
/// <summary>
61+
/// Adds a request header to the response.
62+
/// </summary>
63+
/// <param name="header">The name of the header to add.</param>
64+
/// <param name="value">The value of the header to add.</param>
65+
/// <returns>The <see cref="HttpResponseMessageBuilder"/> for further building of the response.</returns>
66+
public HttpResponseMessageBuilder WithHeader(string header, string value)
67+
{
68+
if (string.IsNullOrEmpty(header))
69+
{
70+
throw new ArgumentNullException(nameof(header));
71+
}
72+
73+
httpResponseMessage.Headers.Add(header, value);
74+
return this;
75+
}
76+
77+
/// <summary>
78+
/// Specifies the content of the response.
79+
/// </summary>
80+
/// <param name="content">The <see cref="HttpContent"/> of the response.</param>
81+
/// <returns>The <see cref="HttpResponseMessageBuilder"/> for further building of the response.</returns>
82+
public HttpResponseMessageBuilder WithContent(HttpContent content)
83+
{
84+
httpResponseMessage.Content = content;
85+
return this;
86+
}
87+
88+
/// <summary>
89+
/// Specifies string content for the response.
90+
/// </summary>
91+
/// <param name="content">The content of the response.</param>
92+
/// <returns>The <see cref="HttpResponseMessageBuilder"/> for further building of the response.</returns>
93+
public HttpResponseMessageBuilder WithStringContent(string content)
94+
{
95+
return WithStringContent(content, null, null);
96+
}
97+
98+
/// <summary>
99+
/// Specifies string content for the response, with a specific encoding.
100+
/// </summary>
101+
/// <param name="content">The content of the response.</param>
102+
/// <param name="encoding">The encoding for this response, defaults to utf-8 when null is passed.</param>
103+
/// <returns>The <see cref="HttpResponseMessageBuilder"/> for further building of the response.</returns>
104+
public HttpResponseMessageBuilder WithStringContent(string content, Encoding? encoding)
105+
{
106+
return WithStringContent(content, encoding, null);
107+
}
108+
109+
/// <summary>
110+
/// Specifies string content for the response.
111+
/// </summary>
112+
/// <param name="content">The content of the response.</param>
113+
/// <param name="encoding">The encoding for this response, defaults to utf-8 when null is passed.</param>
114+
/// <param name="mediaType">The mediatype for this response, defaults to text/plain when null is passed.</param>
115+
/// <returns>The <see cref="HttpResponseMessageBuilder"/> for further building of the response.</returns>
116+
public HttpResponseMessageBuilder WithStringContent(string content, Encoding? encoding, string? mediaType)
117+
{
118+
if (content == null)
119+
{
120+
throw new ArgumentNullException(nameof(content));
121+
}
122+
123+
return WithContent(new StringContent(content, encoding, mediaType));
124+
}
125+
126+
/// <summary>
127+
/// Specifies json content for the response.
128+
/// </summary>
129+
/// <param name="jsonObject">The json object that should be serialized.</param>
130+
/// <returns>The <see cref="HttpResponseMessageBuilder"/> for further building of the response.</returns>
131+
public HttpResponseMessageBuilder WithJsonContent(object? jsonObject)
132+
{
133+
return WithJsonContent(jsonObject, null);
134+
}
135+
136+
/// <summary>
137+
/// Specifies json content for the response.
138+
/// </summary>
139+
/// <param name="jsonObject">The json object that should be serialized.</param>
140+
/// <param name="mediaType">The media type for this content, defaults to application/json when null is passed.</param>
141+
/// <returns>The <see cref="HttpResponseMessageBuilder"/> for further building of the response.</returns>
142+
public HttpResponseMessageBuilder WithJsonContent(object? jsonObject, string? mediaType)
143+
{
144+
var json = JsonSerializer.SerializeToUtf8Bytes(jsonObject);
145+
146+
var content = new ByteArrayContent(json);
147+
content.Headers.ContentType = new MediaTypeHeaderValue(mediaType ?? "application/json");
148+
149+
return WithContent(content);
150+
}
151+
152+
/// <summary>
153+
/// Specifies the request message resulting in this response.
154+
/// </summary>
155+
/// <param name="requestMessage">The <see cref="HttpRequestMessage"/> resulting in this response.</param>
156+
/// <returns>The <see cref="HttpResponseMessageBuilder"/> for further building of the response.</returns>
157+
public HttpResponseMessageBuilder WithRequestMessage(HttpRequestMessage requestMessage)
158+
{
159+
httpResponseMessage.RequestMessage = requestMessage;
160+
return this;
161+
}
162+
163+
/// <summary>
164+
/// Builds and returns the HttpResponseMessage.
165+
/// </summary>
166+
/// <returns>The <see cref="HttpResponseMessage"/></returns>
167+
public HttpResponseMessage Build()
168+
{
169+
return httpResponseMessage;
170+
}
171+
}
172+
}

src/HttpClientTestHelpers/TestableHttpMessageHandler.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ public void RespondWith(HttpResponseMessage httpResponseMessage)
4444
response = httpResponseMessage ?? throw new ArgumentNullException(nameof(httpResponseMessage));
4545
}
4646

47+
/// <summary>
48+
/// Configure the <see cref="HttpResponseMessage"/> that should be returned for each request using a <see cref="HttpResponseMessageBuilder"/>.
49+
/// </summary>
50+
/// <param name="httpResponseMessageBuilderAction">An action that calls methods on the <see cref="HttpResponseMessageBuilder"/>.</param>
51+
public void RespondWith(Action<HttpResponseMessageBuilder> httpResponseMessageBuilderAction)
52+
{
53+
if (httpResponseMessageBuilderAction == null)
54+
{
55+
throw new ArgumentNullException(nameof(httpResponseMessageBuilderAction));
56+
}
57+
58+
var builder = new HttpResponseMessageBuilder();
59+
httpResponseMessageBuilderAction(builder);
60+
response = builder.Build();
61+
}
62+
4763
/// <summary>
4864
/// Simulate a timeout on the request by throwing a TaskCanceledException when a request is received.
4965
/// </summary>

test/HttpClientTestHelpers.IntegrationTests/ConfigureResponses.cs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public async Task UsingTestHandler_WithoutSettingUpResponse_Returns200OKWithoutC
2222
}
2323

2424
[Fact]
25-
public async Task UsingTestHandlerWithCustomRepsonse_ReturnsCustomResponse()
25+
public async Task UsingTestHandlerWithCustomResponse_ReturnsCustomResponse()
2626
{
2727
using var testHandler = new TestableHttpMessageHandler();
2828
using var response = new HttpResponseMessage(HttpStatusCode.Created)
@@ -39,36 +39,41 @@ public async Task UsingTestHandlerWithCustomRepsonse_ReturnsCustomResponse()
3939
}
4040

4141
[Fact]
42-
public async Task UsingTestHandlerWithMultipleCustomRepsonse_ReturnsLastCustomResponse()
42+
public async Task UsingTestHandlerWithCustomResponseUsingBuilder_ReturnsCustomResponse()
4343
{
4444
using var testHandler = new TestableHttpMessageHandler();
45-
using var response = new HttpResponseMessage(HttpStatusCode.Created)
46-
{
47-
Content = new StringContent("HttpClient testing is easy", Encoding.UTF8, "text/plain")
48-
};
49-
using var realResponse = new HttpResponseMessage(HttpStatusCode.NotFound)
45+
testHandler.RespondWith(response =>
5046
{
51-
Content = new StringContent("Not Found")
52-
};
53-
testHandler.RespondWith(response);
54-
testHandler.RespondWith(realResponse);
47+
response.WithStatusCode(HttpStatusCode.Created)
48+
.WithStringContent("HttpClient testing is easy");
49+
});
50+
51+
using var httpClient = new HttpClient(testHandler);
52+
var result = await httpClient.GetAsync("http://httpbin.org/status/201");
53+
54+
Assert.Equal(HttpStatusCode.Created, result.StatusCode);
55+
Assert.Equal("HttpClient testing is easy", await result.Content.ReadAsStringAsync());
56+
}
57+
58+
[Fact]
59+
public async Task UsingTestHandlerWithMultipleCustomRepsonse_ReturnsLastCustomResponse()
60+
{
61+
using var testHandler = new TestableHttpMessageHandler();
62+
testHandler.RespondWith(response => response.WithStatusCode(HttpStatusCode.Created).WithStringContent("HttpClient testing is easy"));
63+
testHandler.RespondWith(response => response.WithStatusCode(HttpStatusCode.NotFound).WithJsonContent("Not Found"));
5564

5665
using var httpClient = new HttpClient(testHandler);
5766
var result = await httpClient.GetAsync("http://httpbin.org/status/201");
5867

5968
Assert.Equal(HttpStatusCode.NotFound, result.StatusCode);
60-
Assert.Equal("Not Found", await result.Content.ReadAsStringAsync());
69+
Assert.Equal("\"Not Found\"", await result.Content.ReadAsStringAsync());
6170
}
6271

6372
[Fact]
6473
public async Task UsingTestHandlerWithCustomResponse_AlwaysReturnsSameCustomResponse()
6574
{
6675
using var testHandler = new TestableHttpMessageHandler();
67-
using var response = new HttpResponseMessage(HttpStatusCode.Created)
68-
{
69-
Content = new StringContent("HttpClient testing is easy", Encoding.UTF8, "text/plain")
70-
};
71-
testHandler.RespondWith(response);
76+
testHandler.RespondWith(response => response.WithStatusCode(HttpStatusCode.Created).WithStringContent("HttpClient testing is easy"));
7277

7378
using var httpClient = new HttpClient(testHandler);
7479
var urls = new[]

0 commit comments

Comments
 (0)