Skip to content

Commit

Permalink
Introduce assertions for StatusCode of HttpResponseMessage (#1737)
Browse files Browse the repository at this point in the history
  • Loading branch information
mu88 committed Jan 10, 2022
1 parent 208638d commit c7fe3e6
Show file tree
Hide file tree
Showing 13 changed files with 634 additions and 1 deletion.
11 changes: 11 additions & 0 deletions Src/FluentAssertions/AssertionExtensions.cs
Expand Up @@ -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;
Expand Down Expand Up @@ -302,6 +303,16 @@ public static NullableBooleanAssertions Should(this bool? actualValue)
return new NullableBooleanAssertions(actualValue);
}

/// <summary>
/// Returns an <see cref="HttpResponseMessageAssertions"/> object that can be used to assert the
/// current <see cref="HttpResponseMessage"/>.
/// </summary>
[Pure]
public static HttpResponseMessageAssertions Should(this HttpResponseMessage actualValue)
{
return new HttpResponseMessageAssertions(actualValue);
}

/// <summary>
/// Returns an <see cref="GuidAssertions"/> object that can be used to assert the
/// current <see cref="Guid"/>.
Expand Down
1 change: 1 addition & 0 deletions Src/FluentAssertions/FluentAssertions.csproj
Expand Up @@ -75,6 +75,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net47'">
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<Reference Include="System.Configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
Expand Down
234 changes: 234 additions & 0 deletions 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
{
/// <summary>
/// Contains a number of methods to assert that a <see cref="HttpResponseMessage"/> is in the expected state.
/// </summary>
[DebuggerNonUserCode]
public class HttpResponseMessageAssertions : HttpResponseMessageAssertions<HttpResponseMessageAssertions>
{
public HttpResponseMessageAssertions(HttpResponseMessage value)
: base(value)
{
}
}

/// <summary>
/// Contains a number of methods to assert that a <see cref="HttpResponseMessage" /> is in the expected state.
/// </summary>
[DebuggerNonUserCode]
public class HttpResponseMessageAssertions<TAssertions> : ObjectAssertions<HttpResponseMessage, TAssertions>
where TAssertions : HttpResponseMessageAssertions<TAssertions>
{
protected HttpResponseMessageAssertions(HttpResponseMessage value)
: base(value)
{
}

/// <summary>
/// Asserts that the <see cref="HttpStatusCode"/> is successful (2xx).
/// </summary>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndConstraint<TAssertions> 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 <null>.");

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>((TAssertions)this);
}

/// <summary>
/// Asserts that the <see cref="HttpStatusCode"/> is redirection (3xx).
/// </summary>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndConstraint<TAssertions> 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 <null>.");

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>((TAssertions)this);
}

/// <summary>
/// Asserts that the <see cref="HttpStatusCode"/> is either client (4xx) or server error (5xx).
/// </summary>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndConstraint<TAssertions> 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 <null>.");

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>((TAssertions)this);
}

/// <summary>
/// Asserts that the <see cref="HttpStatusCode"/> is client error (4xx).
/// </summary>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndConstraint<TAssertions> 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 <null>.");

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>((TAssertions)this);
}

/// <summary>
/// Asserts that the <see cref="HttpStatusCode"/> is server error (5xx).
/// </summary>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndConstraint<TAssertions> 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 <null>.");

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>((TAssertions)this);
}

/// <summary>
/// Asserts that the <see cref="HttpStatusCode"/> is equal to the specified <paramref name="expected"/> value.
/// </summary>
/// <param name="expected">The expected value</param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndConstraint<TAssertions> 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 <null>.", 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>((TAssertions)this);
}

/// <summary>
/// Asserts that the <see cref="HttpStatusCode"/> is not equal to the specified <paramref name="unexpected"/> value.
/// </summary>
/// <param name="unexpected">The unexpected value</param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because"/>.
/// </param>
public AndConstraint<TAssertions> 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 <null>.", 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>((TAssertions)this);
}

private bool IsServerError() => (int)Subject.StatusCode is >= 500 and <= 599;

private bool IsClientError() => (int)Subject.StatusCode is >= 400 and <= 499;
}
}
Expand Up @@ -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) { }
Expand Down Expand Up @@ -1909,6 +1910,22 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> NotBe(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEmpty(string because = "", params object[] becauseArgs) { }
}
public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions<FluentAssertions.Primitives.HttpResponseMessageAssertions>
{
public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { }
}
public class HttpResponseMessageAssertions<TAssertions> : FluentAssertions.Primitives.ObjectAssertions<System.Net.Http.HttpResponseMessage, TAssertions>
where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions<TAssertions>
{
protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { }
public FluentAssertions.AndConstraint<TAssertions> BeRedirection(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeSuccessful(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> HaveClientError(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> HaveError(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> HaveServerError(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { }
}
public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions<FluentAssertions.Primitives.NullableBooleanAssertions>
{
public NullableBooleanAssertions(bool? value) { }
Expand Down
Expand Up @@ -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) { }
Expand Down Expand Up @@ -1909,6 +1910,22 @@ namespace FluentAssertions.Primitives
public FluentAssertions.AndConstraint<TAssertions> NotBe(string unexpected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotBeEmpty(string because = "", params object[] becauseArgs) { }
}
public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions<FluentAssertions.Primitives.HttpResponseMessageAssertions>
{
public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { }
}
public class HttpResponseMessageAssertions<TAssertions> : FluentAssertions.Primitives.ObjectAssertions<System.Net.Http.HttpResponseMessage, TAssertions>
where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions<TAssertions>
{
protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { }
public FluentAssertions.AndConstraint<TAssertions> BeRedirection(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> BeSuccessful(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> HaveClientError(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> HaveError(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> HaveServerError(string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { }
public FluentAssertions.AndConstraint<TAssertions> NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { }
}
public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions<FluentAssertions.Primitives.NullableBooleanAssertions>
{
public NullableBooleanAssertions(bool? value) { }
Expand Down

0 comments on commit c7fe3e6

Please sign in to comment.