Target Framework: net9.0
FluentAssertions extensions for testing REST API domain handlers. Provides expressive assertion methods for verifying result types, status codes, and response content returned from domain handlers.
Testing domain handler results requires checking status codes, result types, and content. Atc.Rest.FluentAssertions makes this intuitive and readable by providing:
- Expressive Assertions: Natural language methods for common scenarios
- Type-Safe Checks: Strongly-typed assertion methods for result types
- Content Verification: Easy comparison of expected vs actual response data
- Error Message Testing: Verify custom error messages in failed results
- Fluent Chaining: Combine multiple assertions for comprehensive testing
Perfect for:
- Testing REST API domain handlers
- Unit testing API business logic
- Integration testing API responses
- Test-driven API development
- Maintaining high test coverage
dotnet add package Atc.Rest.FluentAssertions- .NET 9.0
- Assertion methods for all HTTP status codes (OK, NotFound, BadRequest, etc.)
- Content verification with
WithContent() - Error message verification with
WithErrorMessage() - Support for all ResultBase-derived types
- Integration with FluentAssertions ecosystem
- Readable test failure messages
- FluentAssertions 7.2.0 (version-pinned for licensing)
- Atc.Rest (base library)
The domain handlers generated by ATC always return a type that inherits from ResultBase. That type is the extension point for the following assertion methods:
-
result.Should().BeOkResult(): Verifies that theresultobject is aOkObjectResulttype with a status code of 200. -
result.Should().BeAcceptedResult(): Verifies that theresultobject is aContentResulttype with a status code of 202. -
result.Should().BeNoContentResult(): Verifies that theresultobject is aContentResulttype with a status code of 204. -
result.Should().BeBadRequestResult(): Verifies that theresultobject is aContentResulttype with a status code of 400. -
result.Should().BeNotFoundResult(): Verifies that theresultobject is aContentResulttype with a status code of 404. -
result.Should().BeConflictResult(): Verifies that theresultobject is aContentResulttype with a status code of 409.
To access the result object, use the standard Subject property, e.g. result.Should().BeOkResult().Subject. The usual standard FluentAssertions properties, such as Which, And, are also available where appropriate.
To help verify the content in a result, use the WithContent method, which accepts the expected content directly, e.g. result.Should().BeOkResult().WithContent(expected).
The "error result types", e.g. bad request and not found, also has a WithErrorMessage method, that helps verify a custom error messages.
The following example will show how to test the following a simple GetAllItemsByCategory handler:
public class GetAllItemsByCategoryHandler : IGetAllItemsByCategoryHandler
{
private readonly IRepository repository;
public GetAllItemsByCategoryHandler(IRepository repository)
{
this.repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public Task<GetAllItemsByCategoryResult> ExecuteAsync(
GetAllItemsByCategoryParameters parameters,
CancellationToken cancellationToken = default)
{
if (parameters is null)
{
throw new ArgumentNullException(nameof(parameters));
}
return InvokeExecuteAsync(parameters, cancellationToken);
}
private async Task<GetAllItemsByCategoryResult> InvokeExecuteAsync(
GetAllItemsByCategoryParameters parameters,
CancellationToken cancellationToken)
{
List<Item>? result = await repository.GetByCategory(parameters.Category);
return result is not null
? GetAllItemsByCategoryResult.OK(result)
: GetAllItemsByCategoryResult.NotFound("Invalid category");
}
}This handler basically uses a repository to try to find items based on a category. The repository will return null as the result if the category does not exist.
To verify the data returned from the repository is correctly returned from the handler, do the following:
[Fact]
public async Task Should_Return_OK_And_Content_From_Repository()
{
// Arrange
var items = new[] { new Item() { Category = "Foo" } };
var repository = new FakeRepository(items);
var sut = new GetAllItemsByCategoryHandler(repository);
// Act
var result = await sut.ExecuteAsync(new GetAllItemsByCategoryParameters { Category = "Foo" });
// Assert
result
.Should()
.BeOkResult()
.WithContent(items);
}In the above example, the BeOkResult method is used to verify that the result is a OkObjectResult type with a status code of 200. Then the WithContent method is used to verify that the content in the OK result is the same as the expected items collection.
The WithContent uses the BeEquivalentTo method from FluentAssertions that does a structural comparison, since the content in the OK result is serialized to JSON.
To verify that a 404 NOT FOUND response is returned when the repository returns null, do the following:
[Fact]
public async Task Should_Return_NotFound_When_Category_Isnt_In_Repository()
{
// Arrange
var repository = new FakeRepository();
var sut = new GetAllItemsByCategoryHandler(repository);
// Act
var result = await sut.ExecuteAsync(new GetAllItemsByCategoryParameters { Category = "Bar" });
// Assert
result
.Should()
.BeNotFoundResult()
.WithErrorMessage("Invalid category");
}In the example above, the BeNotFoundResult method is used to check that the result type is a ContentResult type, with a status code of 404.
If you provide a custom error message to the NotFound method in the handler, this can be verified with the WithErrorMessage method, as shown here.
Contributions are welcome! Please see the main repository README for contribution guidelines.