ASP.NET Core Practice Test

โ–ถ

Learning how to integration test ASP.NET Core applications is one of the most valuable skills a .NET developer can build. Unlike unit tests that isolate individual methods, integration tests exercise the full request pipeline โ€” routing, middleware, dependency injection, model binding, and response formatting โ€” all working together in a realistic environment. When you integration test asp.net core projects correctly, you catch entire categories of bugs that unit tests simply cannot surface, such as misconfigured middleware ordering, incorrect route constraints, or broken service registrations.

Learning how to integration test ASP.NET Core applications is one of the most valuable skills a .NET developer can build. Unlike unit tests that isolate individual methods, integration tests exercise the full request pipeline โ€” routing, middleware, dependency injection, model binding, and response formatting โ€” all working together in a realistic environment. When you integration test asp.net core projects correctly, you catch entire categories of bugs that unit tests simply cannot surface, such as misconfigured middleware ordering, incorrect route constraints, or broken service registrations.

The ASP.NET Core team recognized this need early and shipped first-class tooling to support it. The Microsoft.AspNetCore.Mvc.Testing package provides the WebApplicationFactory<TEntryPoint> class, which spins up a real in-memory HTTP server backed by your actual Startup or top-level Program class. This means your integration tests run against the same middleware pipeline, the same service container, and the same configuration system your production code uses โ€” without opening a real TCP port or deploying to a server.

Getting started requires adding the testing NuGet package to your test project, creating a class that inherits from WebApplicationFactory, and writing standard xUnit, NUnit, or MSTest tests that send HTTP requests and assert on the responses. The learning curve is shallow, but mastering the nuances โ€” overriding services, seeding test databases, managing authentication, and parallelizing test runs โ€” takes practice and a solid understanding of how the framework wires everything together behind the scenes.

One common misconception is that integration tests are slow and expensive. In modern ASP.NET Core, a WebApplicationFactory-based test suite with 200 tests often completes in under 10 seconds on a developer laptop because everything runs in-process. There is no network overhead, no Docker startup time, and no external process to manage. The in-memory server handles all HTTP semantics โ€” status codes, headers, cookies, redirects โ€” so your assertions are meaningful and precise.

Another area developers often overlook is database handling during integration tests. Running tests against a real SQL Server or PostgreSQL database provides maximum fidelity, but it requires careful setup and teardown to keep tests isolated. Using Entity Framework Core's in-memory provider or SQLite in-memory mode gives fast, isolated tests at the cost of some SQL dialect fidelity. The right choice depends on your team's priorities, but most production teams use a combination: SQLite for fast local runs and a real database engine in CI pipelines.

This guide covers everything you need to know about integration testing in ASP.NET Core โ€” from the basic WebApplicationFactory setup to advanced patterns like custom IWebHostBuilder configuration, authentication bypass, Testcontainers integration, and parallel test execution. Whether you are working with .NET 6, .NET 7, .NET 8, or the upcoming .NET 9, the core patterns remain consistent because Microsoft has maintained backward compatibility across major versions of the testing infrastructure.

By the end of this guide you will understand the difference between integration tests and end-to-end tests, know how to structure your test projects for maintainability, and be equipped with concrete code patterns you can drop into any ASP.NET Core project immediately. We will also look at common pitfalls โ€” like forgetting to reset shared state between tests or misconfiguring JWT authentication in the test environment โ€” and show you exactly how to avoid them.

ASP.NET Core Integration Testing by the Numbers

โฑ๏ธ
~10s
Typical 200-test suite runtime
๐Ÿ›
40%
Bugs caught only by integration tests
๐Ÿ“ฆ
1 NuGet
Packages needed to start
๐Ÿ”„
.NET 6โ€“9
Supported framework versions
๐Ÿ’ฏ
100%
Real middleware pipeline coverage
Try Free Integration Test ASP.NET Core Practice Questions

Setting Up WebApplicationFactory Step by Step

๐Ÿ“ฆ

Run dotnet add package Microsoft.AspNetCore.Mvc.Testing in your test project. Ensure the test project references your web application project and targets the same .NET TFM. Set <PreserveCompilationContext>true</PreserveCompilationContext> in your web project's csproj if you are on older SDK versions.

๐Ÿ—๏ธ

Inherit from WebApplicationFactory<Program> and override ConfigureWebHost to swap production services for test doubles โ€” replace your real SQL Server with SQLite, swap external HTTP clients with mocks, and configure test-specific appsettings by calling builder.ConfigureAppConfiguration.

โœ๏ธ

Create a test class with a constructor that accepts your WebApplicationFactory via xUnit's IClassFixture<T>. Call factory.CreateClient() to get an HttpClient wired to the in-memory server, then send requests with GetAsync, PostAsync, or SendAsync and assert on response.StatusCode and response body content.

๐ŸŒฑ

Override the factory's ConfigureWebHost method to resolve your DbContext from the built IServiceProvider and call EnsureCreated() followed by your seeding logic. For parallel test runs, use a unique database name per test class to prevent data collisions between concurrently running fixtures.

โœ…

Use response.EnsureSuccessStatusCode() for positive-path tests. Deserialize JSON bodies with response.Content.ReadFromJsonAsync<T>(). For HTML responses, use a library like AngleSharp to query the DOM. Always assert on specific status codes rather than just checking for non-error responses, especially when testing validation and error-handling middleware.

๐Ÿš€

Execute your suite with dotnet test. Enable parallel collection execution in xUnit by setting [assembly: CollectionBehavior(MaxParallelThreads = 4)]. Use ICollectionFixture to share a single factory instance across all test classes in a collection, dramatically reducing startup overhead in large projects.

Configuring test services correctly is where most ASP.NET Core integration test setups either succeed or fall apart. The ConfigureWebHost override inside your custom WebApplicationFactory gives you complete control over the service container before any test runs. The most common pattern is to call services.RemoveAll<DbContext>() followed by services.AddDbContext<AppDbContext>(options => options.UseInMemoryDatabase("TestDb")), which substitutes the production database registration with a fast, isolated in-memory store. This approach keeps your tests hermetic โ€” no external infrastructure required.

However, the in-memory EF Core provider has important limitations developers must understand. It does not enforce relational constraints, does not support raw SQL queries, and does not replicate database-specific behavior like computed columns or triggers. If your application relies heavily on any of these features, SQLite in-memory mode is a better fit. Configure it by calling options.UseSqlite("DataSource=:memory:") and keeping a persistent connection open for the lifetime of the test so SQLite does not discard the schema between operations. This gives you a real SQL dialect with near-zero overhead.

For teams that need full SQL Server fidelity โ€” including stored procedures, JSON columns, or specific isolation levels โ€” Testcontainers for .NET is the right tool. The Testcontainers.MsSql package spins up a Docker container with a real SQL Server instance, waits for it to become healthy, and provides the connection string to your factory. The container starts once per test collection and is torn down after all tests complete, keeping total overhead reasonable even for large suites. Many high-performance teams report full integration test suites with real SQL Server completing in under two minutes using this approach.

Beyond database configuration, you will frequently need to replace outbound HTTP clients. If your ASP.NET Core app calls external APIs, those calls should be intercepted during integration tests. Use services.AddHttpClient<IMyApiClient, MyApiClient>().ConfigurePrimaryHttpMessageHandler(() => new MockHttpMessageHandler()) where MockHttpMessageHandler is a custom handler that returns preconfigured responses. Libraries like RichardSzalay.MockHttp make this pattern clean and expressive, letting you match request URLs and return specific JSON payloads without touching a real network.

Configuration overrides are equally important. Integration tests often need environment-specific settings like reduced token expiry times, disabled rate limiting, or test-only feature flags. The cleanest approach is to add an appsettings.Testing.json file to your web project and call builder.UseEnvironment("Testing") inside ConfigureWebHost. ASP.NET Core's configuration system will merge this file on top of the base appsettings.json, giving your tests a consistent, predictable configuration baseline without polluting production settings.

Shared state is the most common source of flaky integration tests. When multiple test classes share a single WebApplicationFactory through ICollectionFixture, any test that mutates the database or in-memory state can affect subsequent tests. The safest strategy is to wrap each test in a database transaction that gets rolled back after the test completes. With EF Core, this means starting a transaction on the DbContext, performing all operations, asserting on results, then calling Transaction.RollbackAsync() in the test's dispose method. This gives you a clean slate for every test at near-zero cost.

Logging is often underutilized in integration test setups. By default, WebApplicationFactory suppresses most log output to keep test runner output clean. During debugging, you can re-enable verbose logging by calling builder.ConfigureLogging(logging => logging.AddConsole().SetMinimumLevel(LogLevel.Debug)) inside your factory. This outputs the full request processing pipeline to the test console, making it trivial to trace exactly where a 400 or 500 response originated. Disable verbose logging in your default factory and enable it only via an environment variable or test-specific factory subclass to keep CI output readable.

ASP.NET Core Authentication & Authorization
Test your knowledge of ASP.NET Core auth patterns and middleware configuration
ASP.NET Core Authentication & Authorization 2
Advanced scenarios covering JWT, OAuth, claims transformation, and policy-based auth

Integration Test Patterns by Scenario

๐Ÿ“‹ API Endpoints

Testing REST API endpoints with WebApplicationFactory is straightforward. Create an HttpClient via factory.CreateClient(), send a PostAsync with a JsonContent.Create(payload) body, and assert that the response status is 201 Created and the Location header points to the new resource. For list endpoints, deserialize the JSON array and assert on count and specific property values. Always test both the happy path and validation error paths โ€” a 400 response with a proper ProblemDetails body is just as important to verify as a 200.

For endpoints protected by authorization policies, use a custom AuthenticationHandler that automatically authenticates every request as a test user with a predefined set of claims. Register this handler with services.AddAuthentication("Test").AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", _ => { }) inside your factory's ConfigureWebHost. This bypasses real JWT validation while still exercising your authorization policies, role checks, and claims-based access logic โ€” giving you complete coverage without needing to mint real tokens.

๐Ÿ“‹ Razor Pages / MVC

Testing server-rendered Razor Pages and MVC controllers requires handling anti-forgery tokens for POST requests. Call factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }) to control redirect behavior precisely. For forms, first GET the page to extract the __RequestVerificationToken from the HTML using a parser like AngleSharp, then include that token in the subsequent POST body. This mirrors how a real browser interacts with your application, catching anti-forgery configuration bugs that would only surface in production.

View rendering bugs โ€” missing view files, incorrect model types passed to views, null reference exceptions in Razor templates โ€” are only catchable through integration tests. A GET request that returns a 200 status with HTML content confirms the entire rendering pipeline completed successfully. Parse the returned HTML to assert that specific elements exist, such as a table row for each expected data item or a validation error message for a specific form field. This level of assertion provides far more confidence than a unit test that mocks the view engine entirely.

๐Ÿ“‹ Background Services

Testing hosted services and background workers in ASP.NET Core requires a slightly different approach because these services run independently of the HTTP pipeline. Use factory.Services.GetRequiredService<IMyBackgroundQueue>() to enqueue work directly, then poll for the expected side effect โ€” typically a database record created or an outbound message sent to a mock message broker. Add a short Task.Delay with a retry loop rather than a fixed sleep to keep tests fast while still giving the worker time to process the queued item before assertion.

For event-driven architectures using MediatR or a message bus, replace the real bus with an in-memory fake during integration tests. This keeps tests fast and deterministic while still exercising your handler logic, validation pipeline, and domain events. Libraries like MassTransit.Testing provide an in-memory test harness that publishes messages synchronously during tests, making it trivial to assert that specific events were published as a result of an HTTP request without standing up a real RabbitMQ or Azure Service Bus instance.

Integration Tests vs Unit Tests: Which Should You Prioritize?

Pros

  • Catches middleware ordering bugs that unit tests cannot detect
  • Validates the full dependency injection container configuration
  • Tests real HTTP semantics including status codes, headers, and cookies
  • Exercises routing constraints and parameter binding together
  • Finds configuration errors that only manifest at runtime
  • Provides high confidence that the full request pipeline works end-to-end

Cons

  • Slower than unit tests due to application startup overhead
  • Requires more setup code to configure test factories and seed data
  • Shared state between tests can cause intermittent failures if not managed carefully
  • Database seeding and teardown adds complexity to the test infrastructure
  • Harder to pinpoint the exact failure location compared to focused unit tests
  • Can be over-used, leading to slow suites when unit tests would have been sufficient
ASP.NET Core Authentication & Authorization 3
Deep-dive questions on cookie auth, external providers, and custom middleware
ASP.NET Core Configuration & Environments
Practice questions covering appsettings, environment variables, and secrets management

ASP.NET Core Integration Testing Setup Checklist

Add Microsoft.AspNetCore.Mvc.Testing NuGet package to your test project
Create a custom WebApplicationFactory inheriting from WebApplicationFactory<Program>
Override ConfigureWebHost to replace production databases with test equivalents
Set UseEnvironment("Testing") and add an appsettings.Testing.json config file
Register a TestAuthHandler for endpoints requiring authentication
Replace all outbound HttpClient calls with MockHttpMessageHandler instances
Use IClassFixture to share the factory across tests in the same class
Use ICollectionFixture when sharing the factory across multiple test classes
Implement transaction rollback or database reset between each test method
Verify both success responses and error responses including 400 ProblemDetails bodies
Share One WebApplicationFactory Per Collection

The biggest performance win in ASP.NET Core integration testing is creating the WebApplicationFactory once and reusing it across all tests in a collection. Application startup โ€” building the service container, loading configuration, and initializing middleware โ€” typically takes 1-3 seconds. Multiplied across 50 test classes, that is up to 150 seconds of wasted time. By using ICollectionFixture<CustomWebApplicationFactory> with an xUnit collection definition, you pay the startup cost exactly once and share the in-memory server across hundreds of tests, slashing your total suite runtime dramatically.

Authentication and authorization testing is one of the trickiest areas of ASP.NET Core integration testing because the real authentication middleware validates cryptographic tokens, interacts with external identity providers, and enforces complex policy rules. You almost never want to use real JWT tokens or real OAuth flows in integration tests โ€” the setup complexity is enormous and the tests become fragile, breaking whenever certificates rotate or identity provider endpoints change. Instead, the recommended approach is to inject a fake authentication handler that always succeeds and populates the HttpContext.User with test claims you control completely.

The canonical pattern involves creating a class called TestAuthHandler that inherits from AuthenticationHandler<AuthenticationSchemeOptions> and overrides HandleAuthenticateAsync to return an AuthenticateResult.Success with a ClaimsPrincipal built from a fixed set of claims. Register this handler in your factory's ConfigureWebHost with a scheme name like "Test", and add it as the default authentication scheme. Add WithWebHostBuilder overrides in individual tests to swap in different claim sets โ€” for example, testing an admin endpoint requires claims with role: admin, while testing a user-specific endpoint needs claims with a specific sub value matching your test data.

For applications using ASP.NET Core's built-in authorization policies, the test auth handler approach works seamlessly because policies evaluate the ClaimsPrincipal on the current HttpContext, not the authentication mechanism that produced it. A policy requiring "Permission": "orders:write" will succeed in a test if your TestAuthHandler includes that claim, giving you full coverage of your authorization logic without touching a real identity server.

Cookie-based authentication testing is slightly different. For Razor Pages and MVC applications using cookie auth, you need to perform a login request first, capture the authentication cookie from the response, and include it in subsequent requests. The WebApplicationFactoryClientOptions class has a HandleCookies = true property that makes the test HttpClient behave like a browser โ€” it automatically stores and sends cookies across requests. This lets you write integration tests that simulate a complete login flow, including redirect handling and cookie-based session management.

Role-based access control tests are particularly valuable as integration tests because they verify the entire chain from HTTP request to authorization policy evaluation to response. Write explicit tests for the boundary conditions: an authenticated user without the required role receives a 403 Forbidden, an unauthenticated request to a protected endpoint receives a 401 Unauthorized and the correct WWW-Authenticate header, and an authorized user with the correct role receives a 200 OK with the expected response body. These three test cases together provide strong evidence that your authorization middleware is wired up correctly.

Multi-tenant applications add another dimension to authentication testing. If your app uses tenant-based routing or tenant claims to scope data access, your test auth handler needs to inject the correct tenant identifier claim for each test scenario. Consider creating factory extension methods like factory.CreateClientForTenant("tenant-abc") that return a preconfigured HttpClient with the tenant claim set appropriately. This makes test code readable and reduces the boilerplate of configuring custom auth headers on every request in every test method.

OpenID Connect and OAuth 2.0 callback testing requires special handling because these flows involve redirects to external providers. The safest integration test strategy is to mock the external provider entirely โ€” use a WireMock.Net or similar HTTP stubbing server configured to respond with valid OIDC tokens when your app's redirect URI is called. This lets you test the full OAuth callback processing logic, including token validation, claims extraction, and user creation or update logic, without depending on any external service being available during your CI pipeline runs.

Running integration tests efficiently in a CI/CD pipeline requires thoughtful configuration that balances test fidelity against build time. The most impactful optimization is parallelization. By default, xUnit runs test classes in parallel but methods within a class sequentially. For integration test suites where each test class shares a factory instance via ICollectionFixture, you get excellent parallelism without risking data collisions. Configure the degree of parallelism in xunit.runner.json with "maxParallelThreads": 4 โ€” more than the CPU core count rarely helps because the bottleneck is usually I/O and the in-memory database, not computation.

Docker is increasingly the standard for running integration tests with real infrastructure dependencies. Testcontainers for .NET makes this frictionless โ€” add a reference to Testcontainers.PostgreSQL or Testcontainers.MsSql, create a container instance in your IAsyncLifetime fixture, and provide the container's connection string to your WebApplicationFactory. The container starts in approximately 5-15 seconds, runs all tests, and tears down cleanly. GitHub Actions, Azure DevOps, and CircleCI all support Docker-in-Docker, making this pattern work out of the box on every major CI platform with no additional configuration.

Test data management becomes critical as your integration test suite grows beyond a few dozen tests. A well-structured test data strategy uses a combination of fixed seed data loaded once per collection and dynamic data created and cleaned up per test. Fixed seed data โ€” reference tables, lookup values, system configuration โ€” can be loaded in the collection fixture's InitializeAsync method. Dynamic test data should be created at the start of each test with unique identifiers (using Guid.NewGuid() for string IDs works well) and either deleted in a cleanup step or isolated using the transaction rollback pattern described earlier.

Response time assertions are often neglected in integration test suites. Your tests already send real HTTP requests through the full pipeline, so measuring response time costs nothing extra. Wrap each HttpClient call with a Stopwatch and assert that the elapsed time is under an acceptable threshold โ€” for example, under 200 milliseconds for simple CRUD endpoints. These soft performance assertions do not replace proper load testing, but they do catch accidental N+1 query problems introduced by a developer who forgot to add an Include call when adding a new navigation property to a query.

Code coverage from integration tests is a frequently discussed topic. Because integration tests exercise the full request pipeline, they naturally accumulate high line coverage across controllers, services, and data access layers simultaneously. Use dotnet test --collect:"XPlat Code Coverage" to generate coverage reports and feed them into tools like Coverlet and ReportGenerator.

However, be cautious about using coverage as a quality metric for integration tests โ€” 80% line coverage from a handful of broad integration tests may hide important untested edge cases, while a well-designed unit test suite at 70% coverage may provide far more confidence because it targets specific behaviors deliberately.

Flaky tests are the most productivity-destroying problem in integration test suites. A test that sometimes passes and sometimes fails destroys developer trust in the test suite and leads teams to ignore red CI builds โ€” the worst possible outcome for test quality. The top causes of flakiness in ASP.NET Core integration tests are shared mutable state, time-dependent assertions, race conditions in async code, and non-deterministic ordering of results from database queries. For queries where result order matters, always add an explicit OrderBy clause. For time-dependent code, inject a fake ISystemClock or TimeProvider that you control in tests.

Monitoring and observability testing is an emerging practice that integration tests enable uniquely well. Because your WebApplicationFactory runs the full application including middleware, you can assert that specific log messages were emitted, specific metrics were incremented, or specific distributed trace spans were created during request processing. Inject a custom ILoggerProvider that captures log entries to an in-memory list, then assert after each request that the expected log events were recorded at the expected severity levels. This turns your integration tests into a living specification of your application's observability behavior, catching regressions in logging coverage before they reach production.

Practice ASP.NET Core Configuration & Environment Questions

Building a sustainable integration test practice on your team starts with establishing conventions that every developer follows consistently. The single most important convention is naming: test methods should read like specifications. Use the pattern MethodName_StateUnderTest_ExpectedBehavior โ€” for example, CreateOrder_WithValidPayload_Returns201AndLocationHeader or GetUserProfile_WhenUnauthenticated_Returns401. This naming convention makes it immediately clear what each test covers, what precondition it requires, and what it asserts, without needing to read the test body. Teams that adopt this convention consistently report significantly faster debugging when a specific test fails in CI.

Test organization within a project matters for long-term maintainability. Mirror your main project's folder structure in your test project โ€” if your controllers live in Controllers/Orders/, your integration tests for those controllers should live in IntegrationTests/Controllers/Orders/. Create one test file per controller or page model, and group test methods by HTTP verb. This structure makes it obvious where to add new tests when adding new endpoints and makes it easy to find tests when debugging a failure. Avoid the antipattern of a single massive integration test file with hundreds of methods โ€” it becomes unmaintainable quickly.

The HttpClient returned by factory.CreateClient() has a base address configured to http://localhost/ by default. All relative URL requests work correctly, so you can write client.GetAsync("/api/orders") without needing to hardcode the full URL. However, if your application uses HTTPS redirects, configure WebApplicationFactoryClientOptions.AllowAutoRedirect = false and assert that the 301 or 307 redirect response has the correct Location header, rather than following the redirect silently. This tests your HTTPS enforcement middleware explicitly rather than hiding its behavior behind automatic client-side redirect following.

Content negotiation testing is often overlooked but important for APIs that support multiple response formats. Send requests with different Accept headers โ€” application/json, application/xml, text/csv โ€” and assert that the response Content-Type header matches and the body is formatted correctly. For APIs that support application/problem+json for error responses, explicitly test that your exception handling middleware returns properly structured ProblemDetails objects with the correct type, title, status, and detail fields for each error scenario your API handles.

Streaming responses โ€” file downloads, server-sent events, gzip-compressed responses โ€” require special handling in integration tests. For file downloads, assert that the response has a Content-Disposition: attachment header and that the body bytes match expected content. For gzip-compressed responses, set WebApplicationFactoryClientOptions.HandleDecompression = false to receive the compressed bytes and verify compression is actually applied, rather than letting the HttpClient transparently decompress the response before your assertions see it. These edge cases are easy to test with WebApplicationFactory but would require a running server for any other testing approach.

Versioned APIs present an interesting integration testing challenge. When you add API versioning via the Asp.Versioning NuGet package, each version may have different request schemas, response shapes, or behavior. Write separate test classes for each API version, sharing the factory but using version-specific URL patterns or api-version query parameters. When a new version is released, the old version's tests serve as regression tests ensuring backward compatibility is maintained. This is one of the most powerful properties of a comprehensive integration test suite โ€” it makes breaking changes impossible to ship accidentally.

Finally, remember that integration tests are a complement to unit tests, not a replacement. The ideal testing strategy for an ASP.NET Core application uses unit tests for business logic in domain classes and application services, integration tests for the HTTP layer and database interactions, and a small number of end-to-end tests for critical user journeys.

This pyramid structure gives you fast feedback from unit tests during development, confidence in the full pipeline from integration tests during code review, and production-parity validation from end-to-end tests in staging. Teams that invest in all three layers consistently ship with fewer production incidents than teams relying on any single testing approach.

ASP.NET Core Configuration & Environments 2
Advanced configuration patterns including options validation and dynamic reload scenarios
ASP.NET Core Configuration & Environments 3
Expert-level questions on Azure Key Vault, user secrets, and environment-specific pipelines

Asp Net Core Questions and Answers

What is WebApplicationFactory and why do I need it for integration testing?

WebApplicationFactory is a class in the Microsoft.AspNetCore.Mvc.Testing package that creates an in-memory test server backed by your real application's startup code. It lets you send actual HTTP requests through your full middleware pipeline, service container, and routing configuration without deploying to a real server or opening a TCP port. It is the standard, Microsoft-recommended way to write integration tests for ASP.NET Core applications and supports .NET 6 through .NET 9.

Should I use the EF Core in-memory database or SQLite for integration tests?

Use SQLite in-memory mode for most integration test scenarios. The EF Core InMemory provider skips relational constraint enforcement and does not support raw SQL, which can hide real bugs. SQLite supports standard SQL including foreign keys and most common query patterns at nearly zero overhead. Reserve real SQL Server connections โ€” typically via Testcontainers โ€” for tests that specifically exercise database-engine features like stored procedures, JSON columns, or specific transaction isolation level behaviors.

How do I test endpoints protected by JWT authentication?

The recommended approach is to register a custom TestAuthHandler that inherits from AuthenticationHandler and always returns a successful authentication result with predefined test claims. Add it in your WebApplicationFactory's ConfigureWebHost using AddAuthentication('Test').AddScheme<AuthenticationSchemeOptions, TestAuthHandler>. This lets you control exactly which claims are present per test without minting real tokens, and it fully exercises your authorization policies since they evaluate the ClaimsPrincipal regardless of how it was created.

Why are my integration tests flaky when run in parallel?

Flakiness in parallel integration tests almost always comes from shared mutable state. Common culprits include a shared DbContext instance accessed from multiple threads, database records created by one test being read or modified by another, and non-deterministic result ordering from database queries. Fix these by using a unique in-memory database name per test class, wrapping each test in a transaction that rolls back on dispose, and always adding OrderBy clauses to queries where result order matters for your assertions.

How do I replace external HTTP API calls in integration tests?

Override the HttpClient registration in your WebApplicationFactory's ConfigureWebHost to use a custom HttpMessageHandler that returns predetermined responses. The RichardSzalay.MockHttp library provides a fluent API for matching specific request URLs and returning configured HttpResponseMessage objects. Call services.AddHttpClient<IMyClient, MyClient>().ConfigurePrimaryHttpMessageHandler(() => yourMockHandler) to wire it up. This prevents real network calls during tests while still exercising all your client-side retry, deserialization, and error-handling logic.

What is the difference between IClassFixture and ICollectionFixture in xUnit?

IClassFixture creates one instance of the fixture per test class and shares it across all test methods in that class. ICollectionFixture creates one instance per collection and shares it across all test classes in that collection. For WebApplicationFactory, use IClassFixture when each test class needs independent application configuration. Use ICollectionFixture when multiple test classes can safely share one factory instance โ€” this dramatically reduces total startup time for large test suites by booting the application only once.

Can I test Razor Pages and MVC views with WebApplicationFactory?

Yes. Send GET requests to Razor Pages or MVC controller URLs and assert that the response status is 200 OK and the Content-Type is text/html. Use a library like AngleSharp to parse the response HTML and query specific elements โ€” table rows, form fields, error messages, and navigation links. For POST requests with forms, first GET the page to extract the anti-forgery token, then include it in your POST body. This tests the complete rendering pipeline including model binding, validation, and Razor template execution.

How do I run integration tests with a real database in CI/CD?

Use Testcontainers for .NET, which spins up Docker containers for SQL Server, PostgreSQL, or MySQL during test execution. Add the relevant Testcontainers package, create a container in your IAsyncLifetime fixture's InitializeAsync method, wait for it to become healthy, and pass the connection string to your WebApplicationFactory. All major CI platforms including GitHub Actions, Azure DevOps, and CircleCI support Docker-in-Docker, making this approach work without any special CI configuration beyond enabling Docker in the pipeline agent.

How do I measure code coverage from integration tests?

Run dotnet test --collect:"XPlat Code Coverage" to generate a Cobertura-format coverage file. Pipe it through the ReportGenerator tool to create HTML reports. Because integration tests exercise multiple layers simultaneously โ€” controllers, services, repositories โ€” they often produce high line coverage with relatively few tests. However, treat coverage as a hygiene metric rather than a quality metric: high coverage does not guarantee meaningful assertions, and deliberate unit tests targeting edge cases provide more confidence than broad integration tests with shallow assertions.

What is the best way to structure a large integration test project?

Mirror your main project's folder structure in the test project โ€” one test file per controller, Razor page, or feature area. Create a shared Fixtures folder for your WebApplicationFactory subclasses, TestAuthHandler, database seeding helpers, and MockHttpMessageHandler configurations. Define xUnit Collections to group test classes that share a factory instance. Use a Builders or Factories namespace for test data builder classes that create valid request payloads with sensible defaults, keeping individual test methods short and focused on the specific behavior being verified.
โ–ถ Start Quiz