From 7160dbdd19e473c345cab461ea623b97f791ff43 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 4 Nov 2023 16:17:23 +0100 Subject: [PATCH] FluentAssertions - WithBody and WithBodyAsJson and WithBodyAsBytes (#1014) * WithBody * . * fix * . * . --- .../IRequestMessage.cs | 8 +- .../WireMockANumberOfCallsAssertions.cs | 3 +- .../WireMockAssertions.UsingMethod.cs | 79 ++++++++ .../Assertions/WireMockAssertions.WithBody.cs | 151 ++++++++++++++ .../Assertions/WireMockAssertions.cs | 103 ++-------- .../Assertions/WireMockReceivedAssertions.cs | 2 +- src/WireMock.Net.FluentAssertions/Usings.cs | 3 + .../WireMock.Net.FluentAssertions.csproj | 4 +- src/WireMock.Net/Util/RegexUtils.cs | 2 +- .../WireMockAssertionsTests.cs | 189 ++++++++++++++++++ .../Util/RegexUtilsTests.cs | 6 +- 11 files changed, 450 insertions(+), 100 deletions(-) create mode 100644 src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.UsingMethod.cs create mode 100644 src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs create mode 100644 src/WireMock.Net.FluentAssertions/Usings.cs diff --git a/src/WireMock.Net.Abstractions/IRequestMessage.cs b/src/WireMock.Net.Abstractions/IRequestMessage.cs index f87b9ad6..c94d95a6 100644 --- a/src/WireMock.Net.Abstractions/IRequestMessage.cs +++ b/src/WireMock.Net.Abstractions/IRequestMessage.cs @@ -94,23 +94,23 @@ public interface IRequestMessage IBodyData? BodyData { get; } /// - /// The original body as string. Convenience getter for Handlebars. + /// The original body as string. Convenience getter for Handlebars and WireMockAssertions. /// string? Body { get; } /// - /// The body (as JSON object). Convenience getter for Handlebars. + /// The body (as JSON object). Convenience getter for Handlebars and WireMockAssertions. /// object? BodyAsJson { get; } /// - /// The body (as bytearray). Convenience getter for Handlebars. + /// The body (as bytearray). Convenience getter for Handlebars and WireMockAssertions. /// byte[]? BodyAsBytes { get; } #if MIMEKIT /// - /// The original body as MimeMessage. Convenience getter for Handlebars. + /// The original body as MimeMessage. Convenience getter for Handlebars and WireMockAssertions. /// object? BodyAsMimeMessage { get; } #endif diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockANumberOfCallsAssertions.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockANumberOfCallsAssertions.cs index f830be07..d0a50813 100644 --- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockANumberOfCallsAssertions.cs +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockANumberOfCallsAssertions.cs @@ -1,3 +1,4 @@ +using Stef.Validation; using WireMock.Server; // ReSharper disable once CheckNamespace @@ -10,7 +11,7 @@ namespace WireMock.FluentAssertions public WireMockANumberOfCallsAssertions(IWireMockServer server, int callsCount) { - _server = server; + _server = Guard.NotNull(server); _callsCount = callsCount; } diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.UsingMethod.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.UsingMethod.cs new file mode 100644 index 00000000..410ad46f --- /dev/null +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.UsingMethod.cs @@ -0,0 +1,79 @@ +#pragma warning disable CS1591 +using System; +using WireMock.Constants; + +// ReSharper disable once CheckNamespace +namespace WireMock.FluentAssertions; + +public partial class WireMockAssertions +{ + [CustomAssertion] + public AndConstraint UsingConnect(string because = "", params object[] becauseArgs) + => UsingMethod(HttpRequestMethod.CONNECT, because, becauseArgs); + + [CustomAssertion] + public AndConstraint UsingDelete(string because = "", params object[] becauseArgs) + => UsingMethod(HttpRequestMethod.DELETE, because, becauseArgs); + + [CustomAssertion] + public AndConstraint UsingGet(string because = "", params object[] becauseArgs) + => UsingMethod(HttpRequestMethod.GET, because, becauseArgs); + + [CustomAssertion] + public AndConstraint UsingHead(string because = "", params object[] becauseArgs) + => UsingMethod(HttpRequestMethod.HEAD, because, becauseArgs); + + [CustomAssertion] + public AndConstraint UsingOptions(string because = "", params object[] becauseArgs) + => UsingMethod(HttpRequestMethod.OPTIONS, because, becauseArgs); + + [CustomAssertion] + public AndConstraint UsingPost(string because = "", params object[] becauseArgs) + => UsingMethod(HttpRequestMethod.POST, because, becauseArgs); + + [CustomAssertion] + public AndConstraint UsingPatch(string because = "", params object[] becauseArgs) + => UsingMethod(HttpRequestMethod.PATCH, because, becauseArgs); + + [CustomAssertion] + public AndConstraint UsingPut(string because = "", params object[] becauseArgs) + => UsingMethod(HttpRequestMethod.PUT, because, becauseArgs); + + [CustomAssertion] + public AndConstraint UsingTrace(string because = "", params object[] becauseArgs) + => UsingMethod(HttpRequestMethod.TRACE, because, becauseArgs); + + [CustomAssertion] + public AndConstraint UsingAnyMethod(string because = "", params object[] becauseArgs) + => UsingMethod(Any, because, becauseArgs); + + [CustomAssertion] + public AndConstraint UsingMethod(string method, string because = "", params object[] becauseArgs) + { + var any = method == Any; + Func predicate = request => (any && !string.IsNullOrEmpty(request.Method)) || + string.Equals(request.Method, method, StringComparison.OrdinalIgnoreCase); + + var (filter, condition) = BuildFilterAndCondition(predicate); + + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => _requestMessages) + .ForCondition(requests => _callsCount == 0 || requests.Any()) + .FailWith( + "Expected {context:wiremockserver} to have been called using method {0}{reason}, but no calls were made.", + method + ) + .Then + .ForCondition(condition) + .FailWith( + "Expected {context:wiremockserver} to have been called using method {0}{reason}, but didn't find it among the methods {1}.", + _ => method, + requests => requests.Select(request => request.Method) + ); + + _requestMessages = filter(_requestMessages).ToList(); + + return new AndConstraint(this); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs new file mode 100644 index 00000000..051d46aa --- /dev/null +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs @@ -0,0 +1,151 @@ +#pragma warning disable CS1591 +using System; +using System.Collections.Generic; +using WireMock.Matchers; + +// ReSharper disable once CheckNamespace +namespace WireMock.FluentAssertions; + +public partial class WireMockAssertions +{ + private const string MessageFormatNoCalls = "Expected {context:wiremockserver} to have been called using body {0}{reason}, but no calls were made."; + private const string MessageFormat = "Expected {context:wiremockserver} to have been called using body {0}{reason}, but didn't find it among the body {1}."; + + [CustomAssertion] + public AndConstraint WithBody(string body, string because = "", params object[] becauseArgs) + { + return WithBody(new WildcardMatcher(body), because, becauseArgs); + } + + [CustomAssertion] + public AndConstraint WithBodyAsJson(object body, string because = "", params object[] becauseArgs) + { + return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs); + } + + [CustomAssertion] + public AndConstraint WithBodyAsJson(string body, string because = "", params object[] becauseArgs) + { + return WithBodyAsJson(new JsonMatcher(body), because, becauseArgs); + } + + [CustomAssertion] + public AndConstraint WithBodyAsBytes(byte[] body, string because = "", params object[] becauseArgs) + { + return WithBodyAsBytes(new ExactObjectMatcher(body), because, becauseArgs); + } + + [CustomAssertion] + public AndConstraint WithBody(IStringMatcher matcher, string because = "", params object[] becauseArgs) + { + var (filter, condition) = BuildFilterAndCondition(r => r.Body, matcher); + + return ExecuteAssertionWithBodyStringMatcher(matcher, because, becauseArgs, condition, filter, r => r.Body); + } + + [CustomAssertion] + public AndConstraint WithBodyAsJson(IValueMatcher matcher, string because = "", params object[] becauseArgs) + { + var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher); + + return ExecuteAssertionWithBodyAsJsonValueMatcher(matcher, because, becauseArgs, condition, filter, r => r.BodyAsJson); + } + + [CustomAssertion] + public AndConstraint WithBodyAsBytes(ExactObjectMatcher matcher, string because = "", params object[] becauseArgs) + { + var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsBytes, matcher); + + return ExecuteAssertionWithBodyAsBytesExactObjectMatcher(matcher, because, becauseArgs, condition, filter, r => r.BodyAsBytes); + } + + private AndConstraint ExecuteAssertionWithBodyStringMatcher( + IStringMatcher matcher, + string because, + object[] becauseArgs, + Func, bool> condition, + Func, IReadOnlyList> filter, + Func expression + ) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => _requestMessages) + .ForCondition(requests => _callsCount == 0 || requests.Any()) + .FailWith( + MessageFormatNoCalls, + matcher.GetPatterns() + ) + .Then + .ForCondition(condition) + .FailWith( + MessageFormat, + _ => matcher.GetPatterns(), + requests => requests.Select(expression) + ); + + _requestMessages = filter(_requestMessages).ToList(); + + return new AndConstraint(this); + } + + private AndConstraint ExecuteAssertionWithBodyAsJsonValueMatcher( + IValueMatcher matcher, + string because, + object[] becauseArgs, + Func, bool> condition, + Func, IReadOnlyList> filter, + Func expression + ) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => _requestMessages) + .ForCondition(requests => _callsCount == 0 || requests.Any()) + .FailWith( + MessageFormatNoCalls, + matcher.Value + ) + .Then + .ForCondition(condition) + .FailWith( + MessageFormat, + _ => matcher.Value, + requests => requests.Select(expression) + ); + + _requestMessages = filter(_requestMessages).ToList(); + + return new AndConstraint(this); + } + + private AndConstraint ExecuteAssertionWithBodyAsBytesExactObjectMatcher( + ExactObjectMatcher matcher, + string because, + object[] becauseArgs, + Func, bool> condition, + Func, IReadOnlyList> filter, + Func expression + ) + { + Execute.Assertion + .BecauseOf(because, becauseArgs) + .Given(() => _requestMessages) + .ForCondition(requests => _callsCount == 0 || requests.Any()) + .FailWith( + MessageFormatNoCalls, + matcher.ValueAsObject ?? matcher.ValueAsBytes + ) + .Then + .ForCondition(condition) + .FailWith( + MessageFormat, + _ => matcher.ValueAsObject ?? matcher.ValueAsBytes, + requests => requests.Select(expression) + ); + + _requestMessages = filter(_requestMessages).ToList(); + + return new AndConstraint(this); + } +} \ No newline at end of file diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs index 93759edf..1b7e2fa5 100644 --- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.cs @@ -1,17 +1,14 @@ #pragma warning disable CS1591 using System; using System.Collections.Generic; -using System.Linq; -using FluentAssertions; -using FluentAssertions.Execution; -using WireMock.Constants; +using WireMock.Matchers; using WireMock.Server; using WireMock.Types; // ReSharper disable once CheckNamespace namespace WireMock.FluentAssertions; -public class WireMockAssertions +public partial class WireMockAssertions { private const string Any = "*"; private readonly int? _callsCount; @@ -28,9 +25,7 @@ public class WireMockAssertions [CustomAssertion] public AndWhichConstraint AtAbsoluteUrl(string absoluteUrl, string because = "", params object[] becauseArgs) { - Func predicate = request => string.Equals(request.AbsoluteUrl, absoluteUrl, StringComparison.OrdinalIgnoreCase); - - var (filter, condition) = BuildFilterAndCondition(predicate); + var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.AbsoluteUrl, absoluteUrl, StringComparison.OrdinalIgnoreCase)); Execute.Assertion .BecauseOf(because, becauseArgs) @@ -55,9 +50,7 @@ public class WireMockAssertions [CustomAssertion] public AndWhichConstraint AtUrl(string url, string because = "", params object[] becauseArgs) { - Func predicate = request => string.Equals(request.Url, url, StringComparison.OrdinalIgnoreCase); - - var (filter, condition) = BuildFilterAndCondition(predicate); + var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.Url, url, StringComparison.OrdinalIgnoreCase)); Execute.Assertion .BecauseOf(because, becauseArgs) @@ -83,9 +76,7 @@ public class WireMockAssertions [CustomAssertion] public AndWhichConstraint WithProxyUrl(string proxyUrl, string because = "", params object[] becauseArgs) { - Func predicate = request => string.Equals(request.ProxyUrl, proxyUrl, StringComparison.OrdinalIgnoreCase); - - var (filter, condition) = BuildFilterAndCondition(predicate); + var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.ProxyUrl, proxyUrl, StringComparison.OrdinalIgnoreCase)); Execute.Assertion .BecauseOf(because, becauseArgs) @@ -111,9 +102,7 @@ public class WireMockAssertions [CustomAssertion] public AndWhichConstraint FromClientIP(string clientIP, string because = "", params object[] becauseArgs) { - Func predicate = request => string.Equals(request.ClientIP, clientIP, StringComparison.OrdinalIgnoreCase); - - var (filter, condition) = BuildFilterAndCondition(predicate); + var (filter, condition) = BuildFilterAndCondition(request => string.Equals(request.ClientIP, clientIP, StringComparison.OrdinalIgnoreCase)); Execute.Assertion .BecauseOf(because, becauseArgs) @@ -168,80 +157,20 @@ public class WireMockAssertions return new AndConstraint(this); } - [CustomAssertion] - public AndConstraint UsingConnect(string because = "", params object[] becauseArgs) - => UsingMethod(HttpRequestMethod.CONNECT, because, becauseArgs); - - [CustomAssertion] - public AndConstraint UsingDelete(string because = "", params object[] becauseArgs) - => UsingMethod(HttpRequestMethod.DELETE, because, becauseArgs); - - [CustomAssertion] - public AndConstraint UsingGet(string because = "", params object[] becauseArgs) - => UsingMethod(HttpRequestMethod.GET, because, becauseArgs); - - [CustomAssertion] - public AndConstraint UsingHead(string because = "", params object[] becauseArgs) - => UsingMethod(HttpRequestMethod.HEAD, because, becauseArgs); - - [CustomAssertion] - public AndConstraint UsingOptions(string because = "", params object[] becauseArgs) - => UsingMethod(HttpRequestMethod.OPTIONS, because, becauseArgs); - - [CustomAssertion] - public AndConstraint UsingPost(string because = "", params object[] becauseArgs) - => UsingMethod(HttpRequestMethod.POST, because, becauseArgs); - - [CustomAssertion] - public AndConstraint UsingPatch(string because = "", params object[] becauseArgs) - => UsingMethod(HttpRequestMethod.PATCH, because, becauseArgs); - - [CustomAssertion] - public AndConstraint UsingPut(string because = "", params object[] becauseArgs) - => UsingMethod(HttpRequestMethod.PUT, because, becauseArgs); - - [CustomAssertion] - public AndConstraint UsingTrace(string because = "", params object[] becauseArgs) - => UsingMethod(HttpRequestMethod.TRACE, because, becauseArgs); - - [CustomAssertion] - public AndConstraint UsingAnyMethod(string because = "", params object[] becauseArgs) - => UsingMethod(Any, because, becauseArgs); - - [CustomAssertion] - public AndConstraint UsingMethod(string method, string because = "", params object[] becauseArgs) - { - var any = method == Any; - Func predicate = request => (any && !string.IsNullOrEmpty(request.Method)) || - string.Equals(request.Method, method, StringComparison.OrdinalIgnoreCase); - - var (filter, condition) = BuildFilterAndCondition(predicate); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .Given(() => _requestMessages) - .ForCondition(requests => _callsCount == 0 || requests.Any()) - .FailWith( - "Expected {context:wiremockserver} to have been called using method {0}{reason}, but no calls were made.", - method - ) - .Then - .ForCondition(condition) - .FailWith( - "Expected {context:wiremockserver} to have been called using method {0}{reason}, but didn't find it among the methods {1}.", - _ => method, - requests => requests.Select(request => request.Method) - ); - - _requestMessages = filter(_requestMessages).ToList(); - - return new AndConstraint(this); - } - private (Func, IReadOnlyList> Filter, Func, bool> Condition) BuildFilterAndCondition(Func predicate) { Func, IReadOnlyList> filter = requests => requests.Where(predicate).ToList(); return (filter, requests => (_callsCount is null && filter(requests).Any()) || _callsCount == filter(requests).Count); } + + private (Func, IReadOnlyList> Filter, Func, bool> Condition) BuildFilterAndCondition(Func expression, IStringMatcher matcher) + { + return BuildFilterAndCondition(r => matcher.IsMatch(expression(r)).IsPerfect()); + } + + private (Func, IReadOnlyList> Filter, Func, bool> Condition) BuildFilterAndCondition(Func expression, IObjectMatcher matcher) + { + return BuildFilterAndCondition(r => matcher.IsMatch(expression(r)).IsPerfect()); + } } \ No newline at end of file diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockReceivedAssertions.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockReceivedAssertions.cs index eba8fd90..74d68c89 100644 --- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockReceivedAssertions.cs +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockReceivedAssertions.cs @@ -45,7 +45,7 @@ namespace WireMock.FluentAssertions return new WireMockANumberOfCallsAssertions(Subject, callsCount); } - /// + /// protected override string Identifier => "wiremockserver"; } } \ No newline at end of file diff --git a/src/WireMock.Net.FluentAssertions/Usings.cs b/src/WireMock.Net.FluentAssertions/Usings.cs new file mode 100644 index 00000000..8999d126 --- /dev/null +++ b/src/WireMock.Net.FluentAssertions/Usings.cs @@ -0,0 +1,3 @@ +global using System.Linq; +global using FluentAssertions; +global using FluentAssertions.Execution; \ No newline at end of file diff --git a/src/WireMock.Net.FluentAssertions/WireMock.Net.FluentAssertions.csproj b/src/WireMock.Net.FluentAssertions/WireMock.Net.FluentAssertions.csproj index 91c1e6ca..9d32896e 100644 --- a/src/WireMock.Net.FluentAssertions/WireMock.Net.FluentAssertions.csproj +++ b/src/WireMock.Net.FluentAssertions/WireMock.Net.FluentAssertions.csproj @@ -22,7 +22,7 @@ true MIT - 10 + 11 @@ -43,7 +43,7 @@ - + \ No newline at end of file diff --git a/src/WireMock.Net/Util/RegexUtils.cs b/src/WireMock.Net/Util/RegexUtils.cs index 3d1e3742..10d268e0 100644 --- a/src/WireMock.Net/Util/RegexUtils.cs +++ b/src/WireMock.Net/Util/RegexUtils.cs @@ -36,7 +36,7 @@ internal static class RegexUtils { if (useRegexExtended) { - var regexExtended = new RegexExtended(pattern, RegexOptions.None, RegexTimeOut); + var regexExtended = new RegexExtended(pattern!, RegexOptions.None, RegexTimeOut); return (true, regexExtended.IsMatch(input)); } diff --git a/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs b/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs index 4aeca8f6..49605536 100644 --- a/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs +++ b/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs @@ -6,6 +6,7 @@ using System.Net.Http.Headers; using System.Threading.Tasks; using FluentAssertions; using WireMock.FluentAssertions; +using WireMock.Matchers; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Server; @@ -633,6 +634,194 @@ public class WireMockAssertionsTests : IDisposable .AtUrl(_server.Url ?? string.Empty); } + [Fact] + public async Task HaveReceived1Call_WithBodyAsString() + { + // Arrange + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath("/a").UsingPost().WithBody("x")) + .RespondWith(Response.Create().WithBody("A response")); + + // Act + var httpClient = new HttpClient(); + + await httpClient.PostAsync($"{server.Url}/a", new StringContent("x")); + + // Assert + server + .Should() + .HaveReceived(1) + .Calls() + .WithBody("*") + .And + .UsingPost(); + + server + .Should() + .HaveReceived(1) + .Calls() + .WithBody("x") + .And + .UsingPost(); + + server + .Should() + .HaveReceived(0) + .Calls() + .WithBody("") + .And + .UsingPost(); + + server + .Should() + .HaveReceived(0) + .Calls() + .WithBody("y") + .And + .UsingPost(); + + server.Stop(); + } + + [Fact] + public async Task HaveReceived1Call_WithBodyAsJson() + { + // Arrange + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath("/a").UsingPost().WithBodyAsJson(new { x = "y" })) + .RespondWith(Response.Create().WithBody("A response")); + + // Act + var httpClient = new HttpClient(); + + await httpClient.PostAsync($"{server.Url}/a", new StringContent(@"{ ""x"": ""y"" }")); + + // Assert + server + .Should() + .HaveReceived(1) + .Calls() + .WithBodyAsJson(new { x = "y" }) + .And + .UsingPost(); + + server + .Should() + .HaveReceived(1) + .Calls() + .WithBodyAsJson(@"{ ""x"": ""y"" }") + .And + .UsingPost(); + + server + .Should() + .HaveReceived(0) + .Calls() + .WithBodyAsJson(new { x = "?" }) + .And + .UsingPost(); + + server + .Should() + .HaveReceived(0) + .Calls() + .WithBodyAsJson(@"{ ""x"": 1234 }") + .And + .UsingPost(); + + server.Stop(); + } + + [Fact] + public async Task HaveReceived1Call_WithBodyAsBytes() + { + // Arrange + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath("/a").UsingPut().WithBody(new byte[] { 100 })) + .RespondWith(Response.Create().WithBody("A response")); + + // Act + var httpClient = new HttpClient(); + + await httpClient.PutAsync($"{server.Url}/a", new ByteArrayContent(new byte[] { 100 })); + + // Assert + server + .Should() + .HaveReceived(1) + .Calls() + .WithBodyAsBytes(new byte[] { 100 }) + .And + .UsingPut(); + + server + .Should() + .HaveReceived(0) + .Calls() + .WithBodyAsBytes(new byte[0]) + .And + .UsingPut(); + + server + .Should() + .HaveReceived(0) + .Calls() + .WithBodyAsBytes(new byte[] { 42 }) + .And + .UsingPut(); + + server.Stop(); + } + + [Fact] + public async Task HaveReceived1Call_WithBodyAsString_UsingStringMatcher() + { + // Arrange + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath("/a").UsingPost().WithBody("x")) + .RespondWith(Response.Create().WithBody("A response")); + + // Act + var httpClient = new HttpClient(); + + await httpClient.PostAsync($"{server.Url}/a", new StringContent("x")); + + // Assert + server + .Should() + .HaveReceived(1) + .Calls() + .WithBody(new ExactMatcher("x")) + .And + .UsingPost(); + + server + .Should() + .HaveReceived(0) + .Calls() + .WithBody(new ExactMatcher("")) + .And + .UsingPost(); + + server + .Should() + .HaveReceived(0) + .Calls() + .WithBody(new ExactMatcher("y")) + .And + .UsingPost(); + + server.Stop(); + } + public void Dispose() { _server?.Stop(); diff --git a/test/WireMock.Net.Tests/Util/RegexUtilsTests.cs b/test/WireMock.Net.Tests/Util/RegexUtilsTests.cs index d00da00e..e48c3473 100644 --- a/test/WireMock.Net.Tests/Util/RegexUtilsTests.cs +++ b/test/WireMock.Net.Tests/Util/RegexUtilsTests.cs @@ -30,8 +30,7 @@ public class RegexUtilsTests [InlineData(null, "test", false, false)] [InlineData(".*", "test", true, true)] [InlineData("invalid[", "test", false, false)] - public void MatchRegex_WithVariousPatterns_ReturnsExpectedResults( - string? pattern, string input, bool expectedIsValid, bool expectedResult) + public void MatchRegex_WithVariousPatterns_ReturnsExpectedResults(string? pattern, string input, bool expectedIsValid, bool expectedResult) { // Act var (isValidResult, matchResult) = RegexUtils.MatchRegex(pattern, input); @@ -46,8 +45,7 @@ public class RegexUtilsTests [InlineData(null, "test", false, false)] [InlineData(".*", "test", true, true)] [InlineData("invalid[", "test", false, false)] - public void MatchRegex_WithVariousPatternsAndExtendedRegex_ReturnsExpectedResults( - string? pattern, string input, bool expectedIsValid, bool expectedResult) + public void MatchRegex_WithVariousPatternsAndExtendedRegex_ReturnsExpectedResults(string? pattern, string input, bool expectedIsValid, bool expectedResult) { // Act var (isValidResult, matchResult) = RegexUtils.MatchRegex(pattern, input, useRegexExtended: true);