FluentAssertions.Web
This is a FluentAssertions extension for HttpResponseMessage in order help with the Assert part and to extract enough information during the Fail part, so less time in debugging is spent.
Nuget
PM> Install-Package FluentAssertions.Web
Why?
I'm using quite often lately TestSever for functional tests and consequently wrote some repetitive code or spent good time in debugging. With this tool I intend to solve two problems:
Focus on Assert and not on HttpClient related APIs, neither on serialization/deserialization
Once the response is ready you'll want to assert it. With first level properties like StatusCode
is somehow easy, especially with FluentAssertions, but often we need more, like to deserialize the content into an object of a certain type and then to Assert it. Or to simply assert something about the response content itself. Soon duplication code occurs and the urge to reduce it is just the next logical step.
Focus on writing functional tests and not on debugging things
When a test is failing, the following actions are taken most of the time:
- attach the debugger to the line containing
var response = await client..
- Debug the failing test
- add an Watch for
response.Content.ReadAsStringAsync().Result
and see the actual response content
All these don't have to happen if we would get the right diagnostics in the Test Detail Summary screen.
Examples
Asserts that a response is HTTP 200 OK. If the test fails, then the Test Detail Summary will contain request/response information, providing for example a similar experience as when intercepting it with Fiddler.
[Fact]
public async Task Post_ReturnsOk()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.PostAsync("/api/comments", new StringContent(@"{
""author"": ""John"",
""content"": ""Hey, you...""
}", Encoding.UTF8, "application/json"));
// Assert
response.Should().Be200Ok();
}
Other examples
[Fact]
public async Task Get_Returns_Ok_With_CommentsList()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/api/comments");
// Assert
response.Should().Be200Ok().And.BeAs(new[]
{
new { Author = "Adrian", Content = "Hey" }
});
}
[Fact]
public async Task Post_ReturnsOkAndWithContent()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.PostAsync("/api/comments", new StringContent(@"{
""author"": ""John"",
""content"": ""Hey, you...""
}", Encoding.UTF8, "application/json"));
// Assert
response.Should().Be200Ok().And.BeAs(new
{
Author = "John",
Content = "Hey, you..."
});
}
[Fact]
public async Task Post_WithNoAuthorButWithContent_ReturnsBadRequestWithAnErrorMessageRelatedToAuthorOnly()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.PostAsync("/api/comments", new StringContent(@"{
""content"": ""Hey, you...""
}", Encoding.UTF8, "application/json"));
// Assert
response.Should().Be400BadRequest()
.And.OnlyHaveError("Author", "The Author field is required.");
}
[Fact]
public async Task Get_Returns_Ok_With_CommentsList_With_TwoUniqueComments()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/api/comments");
// Assert
response.Should().Satisfy<IReadOnlyCollection<Comment>>(
model =>
{
model.Should().HaveCount(2);
model.Should().OnlyHaveUniqueItems(c => c.CommentId);
}
);
}
[Fact]
public async Task Get_WithCommentId_Returns_A_NonSpam_Comment()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/api/comments/1");
// Assert
response.Should().Satisfy(givenModelStructure: new
{
Author = default(string),
Content = default(string)
}, assertion: model =>
{
model.Author.Should().NotBe("I DO SPAM!");
model.Content.Should().NotContain("BUY MORE");
});
}
Many more examples can be found in the Samples projects and in the Specs files from the FluentAssertions.Web.Tests project
Full API
HttpResponseMessageAssertions | Contains a number of methods to assert that an HttpResponseMessage is in the expected state related to the HTTP content. |
---|---|
Should().BeAs<TModel>() | Asserts that HTTP response content can be an equivalent representation of the expected model. |
Should().HaveHeader() | Asserts that an HTTP response has a named header. |
Should().NotHaveHeader() | Asserts that an HTTP response does not have a named header. |
Should().HaveHttpStatus() | Asserts that a HTTP response has a HTTP status with the specified code. |
Should().NotHaveHttpStatus() | that a HTTP response does not have a HTTP status with the specified code. |
Should().MatchInContent() | Asserts that HTTP response has content that matches a wildcard pattern. |
Should().Satisfy<TModel>() | Asserts that an HTTP response content can be a model that satisfies an assertion. |
Should().Satisfy<HttpResponseMessage>() | Asserts that an HTTP response content can be a model that satisfies an assertion. |
Should().HaveHeader().And. | Contains a number of methods to assert that an HttpResponseMessage is in the expected state related to HTTP headers. |
---|---|
BeEmpty() | Asserts that an existing HTTP header in a HTTP response has no values. |
BeValues() | Asserts that an existing HTTP header in a HTTP response has an expected list of header values. |
Match() | Asserts that an existing HTTP header in a HTTP response contains at least a value that matches a wildcard pattern. |
Should().Be400BadRequest().And. | Contains a number of methods to assert that an HttpResponseMessage is in the expected state related to HTTP Bad Request response |
---|---|
HaveError() | Asserts that a Bad Request HTTP response content contains an error message identifiable by an expected field name and a wildcard error text. |
OnlyHaveError() | Asserts that a Bad Request HTTP response content contains only a single error message identifiable by an expected field name and a wildcard error text. |
NotHaveError() | Asserts that a Bad Request HTTP response content does not contain an error message identifiable by an expected field name and a wildcard error text. |
HaveErrorMessage() | Asserts that a Bad Request HTTP response content contains an error message identifiable by an wildcard error text. |
Fine grained status assertions. | |
---|---|
Should().Be1XXInformational() | Asserts that a HTTP response has a HTTP status code representing an informational response. |
Should().Be2XXSuccessful() | Asserts that a HTTP response has a successful HTTP status code. |
Should().Be4XXClientError() | Asserts that a HTTP response has a HTTP status code representing a client error. |
Should().Be3XXRedirection() | Asserts that a HTTP response has a HTTP status code representing a redirection response. |
Should().Be5XXServerError() | Asserts that a HTTP response has a HTTP status code representing a server error. |
Should().Be100Continue() | Asserts that a HTTP response has the HTTP status 100 Continue |
Should().Be101SwitchingProtocols() | Asserts that a HTTP response has the HTTP status 101 Switching Protocols |
Should().Be200Ok() | Asserts that a HTTP response has the HTTP status 200 Ok |
Should().Be201Created() | Asserts that a HTTP response has the HTTP status 201 Created |
Should().Be202Accepted() | Asserts that a HTTP response has the HTTP status 202 Accepted |
Should().Be203NonAuthoritativeInformation() | Asserts that a HTTP response has the HTTP status 203 Non Authoritative Information |
Should().Be204NoContent() | Asserts that a HTTP response has the HTTP status 204 No Content |
Should().Be205ResetContent() | Asserts that a HTTP response has the HTTP status 205 Reset Content |
Should().Be206PartialContent() | Asserts that a HTTP response has the HTTP status 206 Partial Content |
Should().Be300Ambiguous() | Asserts that a HTTP response has the HTTP status 300 Ambiguous |
Should().Be300MultipleChoices() | Asserts that a HTTP response has the HTTP status 300 Multiple Choices |
Should().Be301Moved() | Asserts that a HTTP response has the HTTP status 301 Moved Permanently |
Should().Be301MovedPermanently() | Asserts that a HTTP response has the HTTP status 301 Moved Permanently |
Should().Be302Found() | Asserts that a HTTP response has the HTTP status 302 Found |
Should().Be302Redirect() | Asserts that a HTTP response has the HTTP status 302 Redirect |
Should().Be303RedirectMethod() | Asserts that a HTTP response has the HTTP status 303 Redirect Method |
Should().Be303SeeOther() | Asserts that a HTTP response has the HTTP status 303 See Other |
Should().Be304NotModified() | Asserts that a HTTP response has the HTTP status 304 Not Modified |
Should().Be305UseProxy() | Asserts that a HTTP response has the HTTP status 305 Use Proxy |
Should().Be306Unused() | Asserts that a HTTP response has the HTTP status 306 Unused |
Should().Be307RedirectKeepVerb() | Asserts that a HTTP response has the HTTP status 307 Redirect Keep Verb |
Should().Be307TemporaryRedirect() | Asserts that a HTTP response has the HTTP status 307 Temporary Redirect |
Should().Be400BadRequest() | Asserts that a HTTP response has the HTTP status 400 BadRequest |
Should().Be401Unauthorized() | Asserts that a HTTP response has the HTTP status 401 Unauthorized |
Should().Be402PaymentRequired() | Asserts that a HTTP response has the HTTP status 402 Payment Required |
Should().Be403Forbidden() | Asserts that a HTTP response has the HTTP status 403 Forbidden |
Should().Be404NotFound() | Asserts that a HTTP response has the HTTP status 404 Not Found |
Should().Be405MethodNotAllowed() | Asserts that a HTTP response has the HTTP status 405 Method Not Allowed |
Should().Be406NotAcceptable() | Asserts that a HTTP response has the HTTP status 406 Not Acceptable |
Should().Be407ProxyAuthenticationRequired() | Asserts that a HTTP response has the HTTP status 407 Proxy Authentication Required |
Should().Be408RequestTimeout() | Asserts that a HTTP response has the HTTP status 408 Request Timeout |
Should().Be409Conflict() | Asserts that a HTTP response has the HTTP status 409 Conflict |
Should().Be410Gone() | Asserts that a HTTP response has the HTTP status 410 Gone |
Should().Be411LengthRequired() | Asserts that a HTTP response has the HTTP status 411 Length Required |
Should().Be412PreconditionFailed() | Asserts that a HTTP response has the HTTP status 412 Precondition Failed |
Should().Be413RequestEntityTooLarge() | Asserts that a HTTP response has the HTTP status 413 Request Entity Too Large |
Should().Be414RequestUriTooLong() | Asserts that a HTTP response has the HTTP status 414 Request Uri Too Long |
Should().Be415UnsupportedMediaType() | Asserts that a HTTP response has the HTTP status 415 Unsupported Media Type |
Should().Be416RequestedRangeNotSatisfiable() | Asserts that a HTTP response has the HTTP status 416 Requested Range Not Satisfiable |
Should().Be417ExpectationFailed() | Asserts that a HTTP response has the HTTP status 417 Expectation Failed |
Should().Be426UpgradeRequired() | Asserts that a HTTP response has the HTTP status 426 UpgradeRequired |
Should().Be500InternalServerError() | Asserts that a HTTP response has the HTTP status 500 Internal Server Error |
Should().Be501NotImplemented() | Asserts that a HTTP response has the HTTP status 501 Not Implemented |
Should().Be502BadGateway() | Asserts that a HTTP response has the HTTP status 502 Bad Gateway |
Should().Be503ServiceUnavailable() | Asserts that a HTTP response has the HTTP status 503 Service Unavailable |
Should().Be504GatewayTimeout() | Asserts that a HTTP response has the HTTP status 504 Gateway Timeout |
Should().Be505HttpVersionNotSupported() | Asserts that a HTTP response has the HTTP status 505 Http Version Not Supported |