Overview
Integration tests confirm that two or more app components work together to produce an expected result, possibly including every component required to fully process a request. They test the app’s infrastructure including database, file system, network appliances, and the request-response pipeline. They use the app’s actual components instead of mocks or fakes.
In ASP.NET integration tests:
- The test project has a reference to the system under test.
- The test project creates a test web host for the SUT and uses a test server client to handle requests and responses with the SUT.
- Reference
Microsoft.AspNetCore.Mvc.Testing
to provide:- Copies the dependencies file (
.deps
) from the SUT into the test project’s/bin
directory. - Set the content root to the SUT’s project root.
- Provide a
WebApplicationFactory
to bootstrap the SUT with aTestServer
.
- Copies the dependencies file (
- Reference
Microsoft.NET.Sdk.Web
- Follow this pattern:
- Initialize
- The SUT’s web host is configured.
- In this step, a different database or perhaps different app settings may be used.
- A test server client is created that submits requests to the app.
- The SUT’s web host is configured.
- Arrange — The test app prepares a request.
- Act — The client submits the request and receives a response.
- Assert — The actual response is compared to the expected response.
- Initialize
razor pages app tests vs mvc app tests
Tests of these two apps are the same — the only thing that differs is how the tests are named.
example
Sample code: https://github.com/dotnet/AspNetCore.Docs.Samples/tree/main/test/integration-tests/IntegrationTestsSample
This example uses xUnit.
// Inheriting from IClassFixture defines the class as containing tests in xUnit:
public class BasicTests : IClassFixture<WebApplicationFactory<Program>>
{
// WebApplicationFactory<TEntryPoint> creates a TestServer; TEntryPoint is the entry point for the SUT.
// The WebApplicationFactory will also provide an HttpClient to the test method:
private readonly WebApplicationFactory<Program> _factory;
public BasicTests(WebApplicationFactory<Program> factory) => _factory = factory;
[Theory]
// Each of these pages will be tested in this test:
[InlineData("/")]
[InlineData("/Index")]
[InlineData("/About")]
[InlineData("/Privacy")]
[InlineData("/Contact")]
public async Task Get_EndpointsReturnSuccessAndCorrectContentType(string url)
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8", response.Content.Headers.ContentType.ToString());
}
}
antiforgery checks
There are two approaches for antiforgery checks:
- Lower level approach: use Application Parts to inject a controller or Razor Page into the app to make JSON requests for the required values. See this blog post for details.
- High level approach: use the AngleSharp parser to conduct antiforgery checks and parsing the HTML.
webapplicationfactory
For tests with the default WebApplicationFactory
, expose the implicit Program
class by updating the SUT’s project file:
<ItemGroup>
<InternalsVisibleTo Include="MyTestProject" />
</ItemGroup>
customizing webapplicationfactory
Web host configuration can also be created independently of the test classes by
Inheriting
WebApplicationFactory
and overridingConfigureWebHost
:public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProgram> where TProgram : class { protected override void ConfigureWebHost(IWebHostBuilder builder) { // This callback is executed after the SUT's Program.cs code is executed: builder.ConfigureServices(services => { // Find the service descriptor for the database context and database connection and remove them: var dbContextDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>)); services.Remove(dbContextDescriptor); var dbConnectionDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbConnection)); services.Remove(dbConnectionDescriptor); // Create open SqliteConnection so EF won't automatically close it: services.AddSingleton<DbConnection>(container => { var connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); return connection; }); services.AddDbContext<ApplicationDbContext>((container, options) => { var connection = container.GetRequiredService<DbConnection>(); options.UseSqlite(connection); }); }); builder.UseEnvironment("Development"); } }
Use the
CustomWebApplicationFactory
in test classes:public class IndexPageTests : IClassFixture<CustomWebApplicationFactory<Program>> { private readonly HttpClient _client; private readonly CustomWebApplicationFactory<Program> _factory; public IndexPageTests(CustomWebApplicationFactory<Program> factory) { _factory = factory; _client = factory.CreateClient(new WebApplicationFactoryClientOptions { // The HttpClient is prevented from following redirects. This allows tests to check the result of the // app's first response: AllowAutoRedirect = false }); }
Here’s a typical test:
[Fact] public async Task Post_DeleteAllMessagesHandler_ReturnsRedirectToRoot() { // Arrange var defaultPage = await _client.GetAsync("/"); var content = await HtmlHelpers.GetDocumentAsync(defaultPage); //Act var response = await _client.SendAsync( (IHtmlFormElement)content.QuerySelector("form[id='messages']"), (IHtmlButtonElement)content.QuerySelector("button[id='deleteAllBtn']")); // Assert Assert.Equal(HttpStatusCode.OK, defaultPage.StatusCode); Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); Assert.Equal("/", response.Headers.Location.OriginalString); }