From da62a43875b207b15fb753516103e067fb99db5a Mon Sep 17 00:00:00 2001 From: Mahmoud Ali Date: Sun, 19 Jul 2020 05:09:07 -0300 Subject: [PATCH] Add fluent assertions for headers (#485) * Add headers assertions * Update FluentAssertions tests with suggested changes --- .../Assertions/WireMockAssertions.cs | 49 +++++++- .../WireMockAssertionsTests.cs | 115 +++++++++++++++++- 2 files changed, 153 insertions(+), 11 deletions(-) diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs index 52f8b12d..dd8f83cb 100644 --- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs @@ -8,19 +8,20 @@ namespace WireMock.FluentAssertions { public class WireMockAssertions { - private readonly IWireMockServer _instance; + private readonly IWireMockServer _subject; - public WireMockAssertions(IWireMockServer instance, int? callsCount) + public WireMockAssertions(IWireMockServer subject, int? callsCount) { - _instance = instance; + _subject = subject; } [CustomAssertion] - public AndConstraint AtAbsoluteUrl(string absoluteUrl, string because = "", params object[] becauseArgs) + public AndConstraint AtAbsoluteUrl(string absoluteUrl, string because = "", + params object[] becauseArgs) { Execute.Assertion .BecauseOf(because, becauseArgs) - .Given(() => _instance.LogEntries.Select(x => x.RequestMessage).ToList()) + .Given(() => _subject.LogEntries.Select(x => x.RequestMessage).ToList()) .ForCondition(requests => requests.Any()) .FailWith( "Expected {context:wiremockserver} to have been called at address matching the absolute url {0}{reason}, but no calls were made.", @@ -33,5 +34,43 @@ namespace WireMock.FluentAssertions return new AndConstraint(this); } + + [CustomAssertion] + public AndConstraint WithHeader(string expectedKey, string value, + string because = "", params object[] becauseArgs) + => WithHeader(expectedKey, new[] {value}, because, becauseArgs); + + [CustomAssertion] + public AndConstraint WithHeader(string expectedKey, string[] expectedValues, + string because = "", params object[] becauseArgs) + { + var headersDictionary = _subject.LogEntries.SelectMany(x => x.RequestMessage.Headers) + .ToDictionary(x => x.Key, x => x.Value); + + using (new AssertionScope("headers from requests sent")) + { + headersDictionary.Should().ContainKey(expectedKey, because, becauseArgs); + } + + using (new AssertionScope($"header \"{expectedKey}\" from requests sent with value(s)")) + { + if (expectedValues.Length == 1) + { + headersDictionary[expectedKey].Should().Contain(expectedValues.First()); + } + else + { + var trimmedHeaderValues = string.Join(",", headersDictionary[expectedKey].Select(x => x)).Split(',') + .Select(x => x.Trim()) + .ToList(); + foreach (var expectedValue in expectedValues) + { + trimmedHeaderValues.Should().Contain(expectedValue); + } + } + } + + return new AndConstraint(this); + } } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs b/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs index d50a4e94..e9b45a30 100644 --- a/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs +++ b/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs @@ -1,12 +1,15 @@ using FluentAssertions; using System; +using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Server; using Xunit; using WireMock.FluentAssertions; using System.Threading.Tasks; +using static System.Environment; namespace WireMock.Net.Tests.FluentAssertions { @@ -14,15 +17,26 @@ namespace WireMock.Net.Tests.FluentAssertions { private WireMockServer _server; private HttpClient _httpClient; - private const int Port = 42000; + private int _portUsed; public WireMockAssertionsTests() { - _server = WireMockServer.Start(Port); + _server = WireMockServer.Start(); _server.Given(Request.Create().UsingAnyMethod()) - .RespondWith(Response.Create().WithStatusCode(200)); + .RespondWith(Response.Create().WithStatusCode(200)); + _portUsed = _server.Ports.First(); - _httpClient = new HttpClient { BaseAddress = new Uri($"http://localhost:{Port}") }; + _httpClient = new HttpClient { BaseAddress = new Uri($"http://localhost:{_portUsed}") }; + } + + [Fact] + public async Task AtAbsoluteUrl_WhenACallWasMadeToAbsoluteUrl_Should_BeOK() + { + await _httpClient.GetAsync("anyurl"); + + _server.Should() + .HaveReceivedACall() + .AtAbsoluteUrl($"http://localhost:{_portUsed}/anyurl"); } [Fact] @@ -50,7 +64,96 @@ namespace WireMock.Net.Tests.FluentAssertions act.Should().Throw() .And.Message.Should() .Be( - $"Expected _server to have been called at address matching the absolute url \"anyurl\", but didn't find it among the calls to {{\"http://localhost:{Port}/\"}}."); + $"Expected _server to have been called at address matching the absolute url \"anyurl\", but didn't find it among the calls to {{\"http://localhost:{_portUsed}/\"}}."); + } + + [Fact] + public async Task WithHeader_WhenACallWasMadeWithExpectedHeader_Should_BeOK() + { + _httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer a"); + await _httpClient.GetAsync(""); + + _server.Should() + .HaveReceivedACall() + .WithHeader("Authorization", "Bearer a"); + } + + [Fact] + public async Task WithHeader_WhenACallWasMadeWithExpectedHeaderAmongMultipleHeaderValues_Should_BeOK() + { + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + await _httpClient.GetAsync(""); + + _server.Should() + .HaveReceivedACall() + .WithHeader("Accept", new[] { "application/xml", "application/json" }); + } + + [Fact] + public async Task WithHeader_Should_ThrowWhenNoCallsMatchingTheHeaderNameWereMade() + { + await _httpClient.GetAsync(""); + + Action act = () => _server.Should() + .HaveReceivedACall() + .WithHeader("Authorization", "value"); + + var sentHeaders = _server.LogEntries.SelectMany(x => x.RequestMessage.Headers) + .ToDictionary(x => x.Key, x => x.Value) + .ToList(); + + var sentHeaderString = "{" + string.Join(", ", sentHeaders) + "}"; + + act.Should().Throw() + .And.Message.Should() + .Be( + $"Expected headers from requests sent {sentHeaderString} to contain key \"Authorization\".{NewLine}"); + } + + [Fact] + public async Task WithHeader_Should_ThrowWhenNoCallsMatchingTheHeaderValuesWereMade() + { + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + await _httpClient.GetAsync(""); + + Action act = () => _server.Should() + .HaveReceivedACall() + .WithHeader("Accept", "missing-value"); + + var sentHeaders = _server.LogEntries.SelectMany(x => x.RequestMessage.Headers) + .ToDictionary(x => x.Key, x => x.Value)["Accept"] + .Select(x => $"\"{x}\"") + .ToList(); + + var sentHeaderString = "{" + string.Join(", ", sentHeaders) + "}"; + + act.Should().Throw() + .And.Message.Should() + .Be( + $"Expected header \"Accept\" from requests sent with value(s) {sentHeaderString} to contain \"missing-value\".{NewLine}"); + } + + [Fact] + public async Task WithHeader_Should_ThrowWhenNoCallsMatchingTheHeaderWithMultipleValuesWereMade() + { + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + await _httpClient.GetAsync(""); + + Action act = () => _server.Should() + .HaveReceivedACall() + .WithHeader("Accept", new[] { "missing-value1", "missing-value2" }); + + const string missingValue1Message = + "Expected header \"Accept\" from requests sent with value(s) {\"application/xml\", \"application/json\"} to contain \"missing-value1\"."; + const string missingValue2Message = + "Expected header \"Accept\" from requests sent with value(s) {\"application/xml\", \"application/json\"} to contain \"missing-value2\"."; + + act.Should().Throw() + .And.Message.Should() + .Be($"{string.Join(NewLine, missingValue1Message, missingValue2Message)}{NewLine}"); } public void Dispose() @@ -59,4 +162,4 @@ namespace WireMock.Net.Tests.FluentAssertions _server?.Dispose(); } } -} +} \ No newline at end of file