From c7fe3e6eb438d9cecba5d24e270d4f31a61f2faf Mon Sep 17 00:00:00 2001 From: mu88 <4560672+mu88@users.noreply.github.com> Date: Mon, 10 Jan 2022 10:20:03 +0100 Subject: [PATCH] Introduce assertions for StatusCode of HttpResponseMessage (#1737) --- Src/FluentAssertions/AssertionExtensions.cs | 11 + Src/FluentAssertions/FluentAssertions.csproj | 1 + .../HttpResponseMessageAssertions.cs | 234 +++++++++++++++ .../FluentAssertions/net47.verified.txt | 17 ++ .../netcoreapp2.1.verified.txt | 17 ++ .../netcoreapp3.0.verified.txt | 17 ++ .../netstandard2.0.verified.txt | 17 ++ .../netstandard2.1.verified.txt | 17 ++ Tests/Benchmarks/Benchmarks.csproj | 2 +- .../HttpResponseMessageAssertionSpecs.cs | 271 ++++++++++++++++++ docs/_data/navigation.yml | 2 + docs/_pages/httpresponsemessages.md | 28 ++ docs/_pages/releases.md | 1 + 13 files changed, 634 insertions(+), 1 deletion(-) create mode 100644 Src/FluentAssertions/Primitives/HttpResponseMessageAssertions.cs create mode 100644 Tests/FluentAssertions.Specs/Primitives/HttpResponseMessageAssertionSpecs.cs create mode 100644 docs/_pages/httpresponsemessages.md diff --git a/Src/FluentAssertions/AssertionExtensions.cs b/Src/FluentAssertions/AssertionExtensions.cs index 9eb3e85dfc..4940cffe48 100644 --- a/Src/FluentAssertions/AssertionExtensions.cs +++ b/Src/FluentAssertions/AssertionExtensions.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.IO; using System.Linq.Expressions; +using System.Net.Http; using System.Reflection; using System.Threading.Tasks; using System.Xml.Linq; @@ -302,6 +303,16 @@ public static NullableBooleanAssertions Should(this bool? actualValue) return new NullableBooleanAssertions(actualValue); } + /// + /// Returns an object that can be used to assert the + /// current . + /// + [Pure] + public static HttpResponseMessageAssertions Should(this HttpResponseMessage actualValue) + { + return new HttpResponseMessageAssertions(actualValue); + } + /// /// Returns an object that can be used to assert the /// current . diff --git a/Src/FluentAssertions/FluentAssertions.csproj b/Src/FluentAssertions/FluentAssertions.csproj index 2204810ff1..330b2da149 100644 --- a/Src/FluentAssertions/FluentAssertions.csproj +++ b/Src/FluentAssertions/FluentAssertions.csproj @@ -75,6 +75,7 @@ + diff --git a/Src/FluentAssertions/Primitives/HttpResponseMessageAssertions.cs b/Src/FluentAssertions/Primitives/HttpResponseMessageAssertions.cs new file mode 100644 index 0000000000..45010cb0ce --- /dev/null +++ b/Src/FluentAssertions/Primitives/HttpResponseMessageAssertions.cs @@ -0,0 +1,234 @@ +using System.Diagnostics; +using System.Net; +using System.Net.Http; +using FluentAssertions.Execution; + +namespace FluentAssertions.Primitives +{ + /// + /// Contains a number of methods to assert that a is in the expected state. + /// + [DebuggerNonUserCode] + public class HttpResponseMessageAssertions : HttpResponseMessageAssertions + { + public HttpResponseMessageAssertions(HttpResponseMessage value) + : base(value) + { + } + } + + /// + /// Contains a number of methods to assert that a is in the expected state. + /// + [DebuggerNonUserCode] + public class HttpResponseMessageAssertions : ObjectAssertions + where TAssertions : HttpResponseMessageAssertions + { + protected HttpResponseMessageAssertions(HttpResponseMessage value) + : base(value) + { + } + + /// + /// Asserts that the is successful (2xx). + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be successful (2xx){reason}, but HttpResponseMessage was ."); + + if (success) + { + Execute.Assertion + .ForCondition(Subject!.IsSuccessStatusCode) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be successful (2xx){reason}, but found {0}.", Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is redirection (3xx). + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint BeRedirection(string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be redirection (3xx){reason}, but HttpResponseMessage was ."); + + if (success) + { + Execute.Assertion + .ForCondition((int)Subject!.StatusCode is >= 300 and <= 399) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be redirection (3xx){reason}, but found {0}.", Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is either client (4xx) or server error (5xx). + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint HaveError(string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be an error{reason}, but HttpResponseMessage was ."); + + if (success) + { + Execute.Assertion + .ForCondition(IsClientError() || IsServerError()) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be an error{reason}, but found {0}.", Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is client error (4xx). + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint HaveClientError(string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be client error (4xx){reason}, but HttpResponseMessage was ."); + + if (success) + { + Execute.Assertion + .ForCondition(IsClientError()) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be client error (4xx){reason}, but found {0}.", Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is server error (5xx). + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint HaveServerError(string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be server error (5xx){reason}, but HttpResponseMessage was ."); + + if (success) + { + Execute.Assertion + .ForCondition(IsServerError()) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be server error (5xx){reason}, but found {0}.", Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is equal to the specified value. + /// + /// The expected value + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint HaveStatusCode(HttpStatusCode expected, string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be {0}{reason}, but HttpResponseMessage was .", expected); + + if (success) + { + Execute.Assertion + .ForCondition(Subject!.StatusCode == expected) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be {0}{reason}, but found {1}.", expected, Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is not equal to the specified value. + /// + /// The unexpected value + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint NotHaveStatusCode(HttpStatusCode unexpected, string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode not to be {0}{reason}, but HttpResponseMessage was .", unexpected); + + if (success) + { + Execute.Assertion + .ForCondition(Subject!.StatusCode != unexpected) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode not to be {0}{reason}, but found {1}.", unexpected, Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + private bool IsServerError() => (int)Subject.StatusCode is >= 500 and <= 599; + + private bool IsClientError() => (int)Subject.StatusCode is >= 400 and <= 499; + } +} diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt index 0f7328406c..8f749545dd 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt @@ -66,6 +66,7 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableGuidAssertions Should(this System.Guid? actualValue) { } public static FluentAssertions.Streams.BufferedStreamAssertions Should(this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } + public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } @@ -1909,6 +1910,22 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions + where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } + } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { public NullableBooleanAssertions(bool? value) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt index 8427581cf0..74366f32aa 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt @@ -66,6 +66,7 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableGuidAssertions Should(this System.Guid? actualValue) { } public static FluentAssertions.Streams.BufferedStreamAssertions Should(this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } + public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } @@ -1909,6 +1910,22 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions + where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } + } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { public NullableBooleanAssertions(bool? value) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt index d0c715644a..742058b3f4 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt @@ -66,6 +66,7 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableGuidAssertions Should(this System.Guid? actualValue) { } public static FluentAssertions.Streams.BufferedStreamAssertions Should(this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } + public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } @@ -1909,6 +1910,22 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions + where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } + } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { public NullableBooleanAssertions(bool? value) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt index 9b13b251b7..9a7e925e43 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt @@ -65,6 +65,7 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableGuidAssertions Should(this System.Guid? actualValue) { } public static FluentAssertions.Streams.BufferedStreamAssertions Should(this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } + public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } @@ -1862,6 +1863,22 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions + where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } + } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { public NullableBooleanAssertions(bool? value) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt index ea94174c5a..27d7e7a226 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt @@ -66,6 +66,7 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableGuidAssertions Should(this System.Guid? actualValue) { } public static FluentAssertions.Streams.BufferedStreamAssertions Should(this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } + public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } @@ -1909,6 +1910,22 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions + where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } + } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { public NullableBooleanAssertions(bool? value) { } diff --git a/Tests/Benchmarks/Benchmarks.csproj b/Tests/Benchmarks/Benchmarks.csproj index 51b8040720..3e61198905 100644 --- a/Tests/Benchmarks/Benchmarks.csproj +++ b/Tests/Benchmarks/Benchmarks.csproj @@ -17,4 +17,4 @@ - \ No newline at end of file + diff --git a/Tests/FluentAssertions.Specs/Primitives/HttpResponseMessageAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/HttpResponseMessageAssertionSpecs.cs new file mode 100644 index 0000000000..d0513cacc7 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/HttpResponseMessageAssertionSpecs.cs @@ -0,0 +1,271 @@ +using System; +using System.Net; +using System.Net.Http; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives +{ + public class HttpResponseMessageAssertionSpecs + { + [Theory] + [InlineData(HttpStatusCode.OK)] + [InlineData(HttpStatusCode.Accepted)] + public void Should_succeed_when_status_code_is_successful(HttpStatusCode statusCodeOfResponse) + { + // Arrange + var testee = new HttpResponseMessage(statusCodeOfResponse); + + // Act / Assert + testee.Should().BeSuccessful(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_status_code_error_is_successful() + { + // Arrange + Action action = () => new HttpResponseMessage(HttpStatusCode.Gone).Should() + .BeSuccessful("because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode to be successful (2xx) because we want to test the failure message, but found HttpStatusCode.Gone {value: 410}."); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_success_but_response_is_null() + { + // Arrange + Action action = () => + ((HttpResponseMessage)null).Should().BeSuccessful("because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode to be successful (2xx) because we want to test the failure message, but HttpResponseMessage was ."); + } + + [Theory] + [InlineData(HttpStatusCode.Moved)] + public void Should_succeed_when_status_code_is_redirect(HttpStatusCode statusCodeOfResponse) + { + // Arrange + var testee = new HttpResponseMessage(statusCodeOfResponse); + + // Act / Assert + testee.Should().BeRedirection(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_status_code_error_is_redirection() + { + // Arrange + Action action = () => new HttpResponseMessage(HttpStatusCode.Gone).Should() + .BeRedirection("because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode to be redirection (3xx) because we want to test the failure message, but found HttpStatusCode.Gone {value: 410}."); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_redirect_but_response_is_null() + { + // Arrange + Action action = () => + ((HttpResponseMessage)null).Should().BeRedirection("because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode to be redirection (3xx) because we want to test the failure message, but HttpResponseMessage was ."); + } + + [Theory] + [InlineData(HttpStatusCode.Gone)] + [InlineData(HttpStatusCode.BadRequest)] + public void Should_succeed_when_status_code_is_client_error(HttpStatusCode statusCodeOfResponse) + { + // Arrange + var testee = new HttpResponseMessage(statusCodeOfResponse); + + // Act / Assert + testee.Should().HaveClientError(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_status_code_success_is_client_error() + { + // Arrange + Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should() + .HaveClientError("because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode to be client error (4xx) because we want to test the failure message, but found HttpStatusCode.OK {value: 200}."); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_client_error_but_response_is_null() + { + // Arrange + Action action = () => + ((HttpResponseMessage)null).Should().HaveClientError("because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode to be client error (4xx) because we want to test the failure message, but HttpResponseMessage was ."); + } + + [Theory] + [InlineData(HttpStatusCode.InternalServerError)] + public void Should_succeed_when_status_code_is_server_error(HttpStatusCode statusCodeOfResponse) + { + // Arrange + var testee = new HttpResponseMessage(statusCodeOfResponse); + + // Act / Assert + testee.Should().HaveServerError(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_status_code_success_is_server_error() + { + // Arrange + Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should() + .HaveServerError("because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode to be server error (5xx) because we want to test the failure message, but found HttpStatusCode.OK {value: 200}."); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_server_error_but_response_is_null() + { + // Arrange + Action action = () => + ((HttpResponseMessage)null).Should().HaveServerError("because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode to be server error (5xx) because we want to test the failure message, but HttpResponseMessage was ."); + } + + [Theory] + [InlineData(HttpStatusCode.BadRequest)] + [InlineData(HttpStatusCode.InternalServerError)] + public void Should_succeed_when_status_code_is_error(HttpStatusCode statusCodeOfResponse) + { + // Arrange + var testee = new HttpResponseMessage(statusCodeOfResponse); + + // Act / Assert + testee.Should().HaveError(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_status_code_success_is_error() + { + // Arrange + Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should() + .HaveError("because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode to be an error because we want to test the failure message, but found HttpStatusCode.OK {value: 200}."); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_error_but_response_is_null() + { + // Arrange + Action action = () => + ((HttpResponseMessage)null).Should().HaveError("because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode to be an error because we want to test the failure message, but HttpResponseMessage was ."); + } + + [Fact] + public void Should_succeed_when_status_code_to_be_equal_to_the_same_value() + { + // Arrange + Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should().HaveStatusCode(HttpStatusCode.OK); + + // Act / Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_status_code_value_to_be_equal_to_a_different_value() + { + // Arrange + Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should().HaveStatusCode(HttpStatusCode.Gone, + "because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode to be HttpStatusCode.Gone {value: 410} because we want to test the failure message, but found HttpStatusCode.OK {value: 200}.*"); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_certain_status_code_but_response_is_null() + { + // Arrange + Action action = () => ((HttpResponseMessage)null).Should() + .HaveStatusCode(HttpStatusCode.Gone, "because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode to be HttpStatusCode.Gone {value: 410} because we want to test the failure message, but HttpResponseMessage was ."); + } + + [Fact] + public void Should_succeed_when_status_code_value_not_to_be_equal_to_the_same_value() + { + // Arrange + Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should().NotHaveStatusCode(HttpStatusCode.Gone); + + // Act / Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_status_code_value_not_to_be_equal_to_a_different_value() + { + // Arrange + Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should().NotHaveStatusCode(HttpStatusCode.OK, + "because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode not to be HttpStatusCode.OK {value: 200} because we want to test the failure message, but found HttpStatusCode.OK {value: 200}.*"); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_against_certain_status_code_but_response_is_null() + { + // Arrange + Action action = () => ((HttpResponseMessage)null).Should() + .NotHaveStatusCode(HttpStatusCode.Gone, "because we want to test the failure {0}", "message"); + + // Act / Assert + action.Should().Throw() + .WithMessage( + "Expected HttpStatusCode not to be HttpStatusCode.Gone {value: 410} because we want to test the failure message, but HttpResponseMessage was ."); + } + } +} diff --git a/docs/_data/navigation.yml b/docs/_data/navigation.yml index 51c26a1224..0ec09d306d 100644 --- a/docs/_data/navigation.yml +++ b/docs/_data/navigation.yml @@ -73,6 +73,8 @@ sidebar: url: /xml - title: Execution Time url: /executiontime + - title: HTTP response messages + url: /httpresponsemessages - title: Extensibility url: /extensibility diff --git a/docs/_pages/httpresponsemessages.md b/docs/_pages/httpresponsemessages.md new file mode 100644 index 0000000000..96575c1087 --- /dev/null +++ b/docs/_pages/httpresponsemessages.md @@ -0,0 +1,28 @@ +--- +title: HttpResponseMessages +permalink: /httpresponsemessages/ +layout: single +classes: wide +sidebar: +nav: "sidebar" +--- + +```csharp +var successfulResponse = new HttpResponseMessage(HttpStatusCode.OK); +successfulResponse.Should().BeSuccessful("it's set to OK"); // (HttpStatusCode = 2xx) + +var redirectResponse = new HttpResponseMessage(HttpStatusCode.Moved); +redirectResponse.Should().BeRedirection("it's set to Moved"); // (HttpStatusCode = 3xx) + +var clientErrorResponse = new HttpResponseMessage(HttpStatusCode.BadRequest); +clientErrorResponse.Should().HaveClientError("it's set to BadRequest"); // (HttpStatusCode = 4xx) +clientErrorResponse.Should().HaveError("it's set to BadRequest"); // (HttpStatusCode = 4xx or 5xx) + +var serverErrorResponse = new HttpResponseMessage(HttpStatusCode.InternalServerError); +serverErrorResponse.Should().HaveServerError("it's set to InternalServerError"); // (HttpStatusCode = 5xx) +serverErrorResponse.Should().HaveError("it's set to InternalServerError"); // (HttpStatusCode = 4xx or 5xx) + +var anotherResponse = new HttpResponseMessage(HttpStatusCode.Moved); +anotherResponse.Should().HaveStatusCode(HttpStatusCode.Moved); +anotherResponse.Should().NotHaveStatusCode(HttpStatusCode.OK); +``` diff --git a/docs/_pages/releases.md b/docs/_pages/releases.md index eb5b94e687..094c140bc6 100644 --- a/docs/_pages/releases.md +++ b/docs/_pages/releases.md @@ -11,6 +11,7 @@ sidebar: ### What's New * Adding `ThatAreStatic()` and `ThatAreNotStatic()` for filtering in method assertions - [#1740](https://github.com/fluentassertions/fluentassertions/pull/1740) +* Adding new assertions for the `HttpStatusCode` of an `HttpResponseMessage` - [#1737](https://github.com/fluentassertions/fluentassertions/pull/1737) ### Fixes * Querying methods on classes, e.g. `typeof(MyController).Methods()`, now also includes static methods - [#1740](https://github.com/fluentassertions/fluentassertions/pull/1740)