Skip to content

Commit 3cd6b24

Browse files
committed
Adds basic request validation.
Added validations: - ShouldHaveMadeRequests - ShouldHaveMadeRequestsTo - ShouldNotHaveMadeRequests - ShouldNotHaveMadeRequestsTo If any of these validations fail, an HttpRequestMessageAssertionException is thrown. The actual knowledge for how to assert what requests are made can be found in HttpRequestMessageAsserter.
1 parent ab2304a commit 3cd6b24

File tree

5 files changed

+312
-0
lines changed

5 files changed

+312
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Net.Http;
5+
6+
namespace HttpClientTestHelpers
7+
{
8+
public class HttpRequestMessageAsserter
9+
{
10+
private readonly List<string> _expectedConditions = new List<string>();
11+
private readonly bool _negate = false;
12+
13+
public HttpRequestMessageAsserter(IEnumerable<HttpRequestMessage> httpRequestMessages)
14+
{
15+
Requests = httpRequestMessages ?? throw new ArgumentNullException(nameof(httpRequestMessages));
16+
}
17+
18+
public HttpRequestMessageAsserter(IEnumerable<HttpRequestMessage> httpRequestMessages, bool negate)
19+
{
20+
Requests = httpRequestMessages ?? throw new ArgumentNullException(nameof(httpRequestMessages));
21+
_negate = negate;
22+
}
23+
24+
public IEnumerable<HttpRequestMessage> Requests { get; private set; }
25+
26+
private void Assert(int? count = null)
27+
{
28+
var actualCount = Requests.Count();
29+
var pass = count switch
30+
{
31+
null => Requests.Any(),
32+
_ => actualCount == count,
33+
};
34+
35+
if (_negate)
36+
{
37+
if (!count.HasValue)
38+
{
39+
count = 0;
40+
}
41+
pass = !pass;
42+
}
43+
44+
if (!pass)
45+
{
46+
var expected = count switch
47+
{
48+
0 => "no requests to be made",
49+
_ => "at least one request to be made",
50+
};
51+
var actual = actualCount switch
52+
{
53+
0 => "no requests were made",
54+
1 => "one request was made",
55+
_ => $"{actualCount} requests were made",
56+
};
57+
58+
if (_expectedConditions.Any())
59+
{
60+
var conditions = string.Join(", ", _expectedConditions);
61+
expected += $" with {conditions}";
62+
}
63+
64+
var message = $"Expected {expected}, but {actual}.";
65+
throw new HttpRequestMessageAssertionException(message);
66+
}
67+
}
68+
69+
public HttpRequestMessageAsserter WithUriPattern(string pattern)
70+
{
71+
if (pattern != "*")
72+
{
73+
_expectedConditions.Add($"uri pattern '{pattern}'");
74+
}
75+
76+
Requests = Requests.Where(x => x.HasMatchingUri(pattern));
77+
Assert();
78+
return this;
79+
}
80+
}
81+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
3+
namespace HttpClientTestHelpers
4+
{
5+
public class HttpRequestMessageAssertionException : Exception
6+
{
7+
private HttpRequestMessageAssertionException()
8+
{
9+
}
10+
11+
public HttpRequestMessageAssertionException(string message) : base(message)
12+
{
13+
}
14+
15+
public HttpRequestMessageAssertionException(string message, Exception innerException) : base(message, innerException)
16+
{
17+
}
18+
}
19+
}

src/HttpClientTestHelpers/TestableHttpMessageHandler.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,51 @@ public void RespondWith(HttpResponseMessage httpResponseMessage)
3535
{
3636
response = httpResponseMessage ?? throw new ArgumentNullException(nameof(httpResponseMessage));
3737
}
38+
39+
/// <summary>
40+
/// Validates that requests have been made, throws an exception when no requests were made.
41+
/// </summary>
42+
public void ShouldHaveMadeRequests()
43+
{
44+
_ = new HttpRequestMessageAsserter(Requests).WithUriPattern("*");
45+
}
46+
47+
/// <summary>
48+
/// Validates that requests to a specific uri have been made, throws an exception when no requests were made.
49+
/// </summary>
50+
/// <param name="pattern">The uri pattern to validate against, the pattern supports *.</param>
51+
/// <returns>An <seealso cref="HttpRequestMessageAsserter"/> which can be used for further validations.</returns>
52+
public HttpRequestMessageAsserter ShouldHaveMadeRequestsTo(string pattern)
53+
{
54+
if (pattern == null)
55+
{
56+
throw new ArgumentNullException(nameof(pattern));
57+
}
58+
59+
return new HttpRequestMessageAsserter(Requests).WithUriPattern(pattern);
60+
}
61+
62+
/// <summary>
63+
/// Validates that no requests have been made, throws an exception when requests were made.
64+
/// </summary>
65+
public void ShouldNotHaveMadeRequests()
66+
{
67+
_ = new HttpRequestMessageAsserter(Requests, true).WithUriPattern("*");
68+
}
69+
70+
/// <summary>
71+
/// Validates that no requests to a specific uri have been made, throws an exception when requests were made.
72+
/// </summary>
73+
/// <param name="pattern">The uri pattern to validate against, the pattern supports *.</param>
74+
/// <returns>An <seealso cref="HttpRequestMessageAsserter"/> which can be used for further validations.</returns>
75+
public HttpRequestMessageAsserter ShouldNotHaveMadeRequestsTo(string pattern)
76+
{
77+
if (pattern == null)
78+
{
79+
throw new ArgumentNullException(nameof(pattern));
80+
}
81+
82+
return new HttpRequestMessageAsserter(Requests, true).WithUriPattern(pattern);
83+
}
3884
}
3985
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Linq;
3+
using System.Net.Http;
4+
5+
using Xunit;
6+
7+
namespace HttpClientTestHelpers.Tests
8+
{
9+
public class HttpRequestMessageAsserterTests
10+
{
11+
#nullable disable
12+
[Fact]
13+
public void Constructor_NullRequestList_ThrowsArgumentNullException()
14+
{
15+
Assert.Throws<ArgumentNullException>(() => new HttpRequestMessageAsserter(null));
16+
}
17+
#nullable restore
18+
19+
[Fact]
20+
public void WithUriPattern_RequestWithMatchingUri_DoesNotThrowException()
21+
{
22+
var sut = new HttpRequestMessageAsserter(new[] { new HttpRequestMessage(HttpMethod.Get, new Uri("https://example.com")) });
23+
24+
sut.WithUriPattern("https://example.com");
25+
}
26+
27+
[Fact]
28+
public void WithUriPattern_RequestWithMatchingUriAndNegationTurnedOn_ThrowsHttpRequestMessageAssertionExceptionWithSpecificMessage()
29+
{
30+
var sut = new HttpRequestMessageAsserter(new[] { new HttpRequestMessage(HttpMethod.Get, new Uri("https://example.com")) }, true);
31+
32+
var exception = Assert.Throws<HttpRequestMessageAssertionException>(() => sut.WithUriPattern("https://example.com"));
33+
Assert.Equal("Expected no requests to be made with uri pattern 'https://example.com', but one request was made.", exception.Message);
34+
}
35+
36+
[Fact]
37+
public void WithUriPattern_RequestWithNotMatchingUri_ThrowsHttpRequestMessageassertionExceptionWithSpecificMessage()
38+
{
39+
var sut = new HttpRequestMessageAsserter(new[] { new HttpRequestMessage(HttpMethod.Get, new Uri("https://example.com")) });
40+
41+
var exception = Assert.Throws<HttpRequestMessageAssertionException>(() => sut.WithUriPattern("https://test.org"));
42+
Assert.Equal("Expected at least one request to be made with uri pattern 'https://test.org', but no requests were made.", exception.Message);
43+
}
44+
45+
[Fact]
46+
public void WithUriPattern_RequestWithStarPatternAndNoRequests_ThrowsHttpRequestMessageassertionExceptionWithSpecificMessage()
47+
{
48+
var sut = new HttpRequestMessageAsserter(Enumerable.Empty<HttpRequestMessage>());
49+
50+
var exception = Assert.Throws<HttpRequestMessageAssertionException>(() => sut.WithUriPattern("*"));
51+
Assert.Equal("Expected at least one request to be made, but no requests were made.", exception.Message);
52+
}
53+
}
54+
}

test/HttpClientTestHelpers.Tests/TestableHttpMessageHandlerTests.cs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,119 @@ public async Task SendAsync_WhenRespondWithIsSet_SetRespondIsUsed()
6464
Assert.Same(response, result);
6565
}
6666

67+
[Fact]
68+
public void ShouldHaveMadeRequests_WhenNoRequestsWereMade_ThrowsHttpRequestMessageAssertionException()
69+
{
70+
using var sut = new TestableHttpMessageHandler();
71+
72+
Assert.Throws<HttpRequestMessageAssertionException>(() => sut.ShouldHaveMadeRequests());
73+
}
74+
75+
[Fact]
76+
public async Task ShouldHaveMadeRequests_WhenRequestsWereMade_DoesNotThrowExceptions()
77+
{
78+
using var sut = new TestableHttpMessageHandler();
79+
using var client = new HttpClient(sut);
80+
81+
_ = await client.GetAsync(new Uri("https://example.com"));
82+
83+
sut.ShouldHaveMadeRequests();
84+
}
85+
86+
[Fact]
87+
public void ShouldHaveMadeRequestsTo_WhenNoRequestsWereMade_ThrowsHttpRequestMessageAssertionException()
88+
{
89+
using var sut = new TestableHttpMessageHandler();
90+
91+
Assert.Throws<HttpRequestMessageAssertionException>(() => sut.ShouldHaveMadeRequestsTo("https://example.com"));
92+
}
93+
94+
[Fact]
95+
public async Task ShouldHaveMadeRequestsTo_WhenMatchinRequestsWereMade_ReturnsHttpRequestMessageAsserter()
96+
{
97+
using var sut = new TestableHttpMessageHandler();
98+
using var client = new HttpClient(sut);
99+
100+
_ = await client.GetAsync(new Uri("https://example.com"));
101+
102+
var result = sut.ShouldHaveMadeRequestsTo("https://example.com");
103+
104+
Assert.NotNull(result);
105+
Assert.IsType<HttpRequestMessageAsserter>(result);
106+
}
107+
108+
[Fact]
109+
public void ShouldNotHaveMadeRequests_WhenNoRequestsWereMade_DoesNotThrowExceptions()
110+
{
111+
using var sut = new TestableHttpMessageHandler();
112+
113+
sut.ShouldNotHaveMadeRequests();
114+
}
115+
116+
[Fact]
117+
public async Task ShouldNotHaveMadeRequests_WhenASingleRequestWasMade_ThrowsHttpRequestMessageAssertionException()
118+
{
119+
using var sut = new TestableHttpMessageHandler();
120+
using var client = new HttpClient(sut);
121+
122+
_ = await client.GetAsync(new Uri("https://example.com"));
123+
124+
var result = Assert.Throws<HttpRequestMessageAssertionException>(() => sut.ShouldNotHaveMadeRequests());
125+
Assert.Equal("Expected no requests to be made, but one request was made.", result.Message);
126+
}
127+
128+
[Fact]
129+
public async Task ShouldNotHaveMadeRequests_WhenMultipleRequestsWereMade_ThrowsHttpRequestMessageAssertionException()
130+
{
131+
using var sut = new TestableHttpMessageHandler();
132+
using var client = new HttpClient(sut);
133+
134+
_ = await client.GetAsync(new Uri("https://example.com"));
135+
_ = await client.GetAsync(new Uri("https://example.com"));
136+
137+
var result = Assert.Throws<HttpRequestMessageAssertionException>(() => sut.ShouldNotHaveMadeRequests());
138+
Assert.Equal("Expected no requests to be made, but 2 requests were made.", result.Message);
139+
}
140+
141+
[Fact]
142+
public void ShouldNotHaveMadeRequestsTo_WhenNoRequestsWereMade_ReturnsHttpRequestMessageAsserter()
143+
{
144+
using var sut = new TestableHttpMessageHandler();
145+
146+
var result = sut.ShouldNotHaveMadeRequestsTo("https://example.com");
147+
148+
Assert.NotNull(result);
149+
Assert.IsType<HttpRequestMessageAsserter>(result);
150+
}
151+
152+
[Fact]
153+
public async Task ShouldNotHaveMadeRequestsTo_WhenMatchinRequestsWereMade_ThrowsHttpRequestMessageAssertionException()
154+
{
155+
using var sut = new TestableHttpMessageHandler();
156+
using var client = new HttpClient(sut);
157+
158+
_ = await client.GetAsync(new Uri("https://example.com"));
159+
160+
Assert.Throws<HttpRequestMessageAssertionException>(() => sut.ShouldNotHaveMadeRequestsTo("https://example.com"));
161+
}
162+
67163
#nullable disable
164+
[Fact]
165+
public void ShouldHaveMadeRequestsTo_WhenGivenPatternIsNull_ThrowsArgumentNullException()
166+
{
167+
using var sut = new TestableHttpMessageHandler();
168+
169+
Assert.Throws<ArgumentNullException>(() => sut.ShouldHaveMadeRequestsTo(null));
170+
}
171+
172+
[Fact]
173+
public void ShouldNotHaveMadeRequestsTo_WhenGivenPatternIsNull_ThrowsArgumentNullException()
174+
{
175+
using var sut = new TestableHttpMessageHandler();
176+
177+
Assert.Throws<ArgumentNullException>(() => sut.ShouldNotHaveMadeRequestsTo(null));
178+
}
179+
68180
[Fact]
69181
public void RespondWith_NullValue_ThrowsArgumentNullException()
70182
{

0 commit comments

Comments
 (0)