MVC functional tests with a fixture pattern.
public class BreakfastItemTests {
private readonly ITestOutputHelper _output;
public BreakfastItemTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public Task When_creating_breakfast_item() =>
new MvcFunctionalTestFixture<Startup>(_output)
.WhenCreating("BreakfastItem", out CreateOrUpdateBreakfastItemRequest request)
.ShouldReturnSuccessfulStatus()
.ShouldReturnEquivalentJson<BreakfastItem, CreateOrUpdateBreakfastItemRequest>(request)
.ShouldReturnJson<BreakfastItem>(r => r.Id.Should().NotBeNullOrEmpty())
.RunAsync();
}
This already reads pretty well but let's break it down:
new MvcFunctionalTestFixture<Startup>(_output)
Configures the test server to use the specified startup class and to send all logs to the xunit test context.
.WhenCreating("BreakfastItem", out CreateOrUpdateBreakfastItemRequest request)
Configures the fixture to send a POST /BreakfastItem
request to the test server once it's up and running. We're also asking the fixture to use AutoFixture to create a new instance of the request class and return it to us in an out parameter.
.ShouldReturnSuccessfulStatus()
Adds an assertion to the fixture that the response has a successful (2xx) code.
.ShouldReturnEquivalentJson<BreakfastItem, CreateOrUpdateBreakfastItemRequest>(request)
.ShouldReturnJson<BreakfastItem>(r => r.Id.Should().NotBeNullOrEmpty())
Both of these add an assertion to the fixture that the response body is JSON that can be deserialized to an instance of BreakfastItem
. The first line adds an assertion that the deserialized response is equivalent to the request object according to FluentAssertions amazing equivalence feature. The second line adds an assertion that the Id
property of the response has been set to something other than null or the empty string.
.RunAsync();
This actually runs the fixture. The configuration will mean that it will:
- Create a new test server to host the API that we're testing.
- Send a
POST /BreakfastItem
request to the API with a JSON request body serialized from the generatedCreateOrUpdateBreakfastItemRequest
. - Assert that the response has a successful (2xx) code.
- Attempt to deserialize the response body as a
BreakfastItem
and run assertions on the deserialized object. - If no exceptions are thrown, do nothing and let the test pass. Otherwise:
- If one exception is thrown => re-throw the exception.
- If multiple exceptions are thrown => throw an aggregate exception containing all thrown exceptions.