ASP.NET Core Testing Interview Questions for Senior .NET Developers (2026)

Testing is one of the clearest signals of seniority in a .NET interview. Junior developers write tests that work. Mid-level developers write tests that are isolated. Senior developers write tests that protect the system โ and can explain exactly why each choice was made, from which test double to use to why WebApplicationFactory beats a real deployment for integration coverage. This guide covers the testing questions that actually come up in senior .NET interviews in 2026, grouped from foundational to advanced.
If you want to see how these patterns work inside a complete production codebase โ with unit tests, integration tests, and a test auth helper all connected โ Chapter 13 of the Zero to Production course walks through every layer in a running ASP.NET Core 10 API.
The theory is only half the story. The patterns covered in this guide go deeper on Patreon โ with annotated, production-ready source code that maps directly to what enterprise teams actually ship.
Basic Questions
What is the difference between unit tests, integration tests, and end-to-end tests in ASP.NET Core?
Unit tests test a single class or method in complete isolation. All dependencies are replaced with test doubles (mocks, stubs, fakes). They run in milliseconds and have no I/O.
Integration tests test how two or more components behave together โ typically with real HTTP routing, real DI resolution, and a real database (often an in-memory or containerised variant). WebApplicationFactory<TProgram> is the standard tool in ASP.NET Core.
End-to-end tests test the running system from the outside, using a real browser or HTTP client against a deployed environment. They are the slowest and most expensive to maintain. Senior developers know when to use each layer and keep the test pyramid proportional: many unit tests, a focused set of integration tests, and minimal E2E tests.
What is WebApplicationFactory and why is it preferred for integration testing?
WebApplicationFactory<TProgram> spins up a full ASP.NET Core application in-process using a TestServer. It resolves the real DI container, runs real middleware, and routes requests through the actual pipeline โ without needing a port or a deployed host. This gives integration tests high confidence without the flakiness of network-dependent tests.
The key capabilities are: overriding services with WithWebHostBuilder or ConfigureWebHost, replacing the real database connection with a test database, injecting test-only services, and creating typed HttpClient instances that talk directly to the in-memory server.
What is a test double? Describe the difference between a mock, a stub, and a fake.
A stub returns hardcoded data. It controls what comes back but has no memory of how it was called.
A mock verifies behaviour โ you assert that a method was called, how many times, with what arguments. Tools like Moq and NSubstitute generate mocks.
A fake is a simplified working implementation โ for example, an in-memory repository that actually stores records, used in place of a real database. Fakes are more maintainable than mocks for stateful collaborators.
Senior developers know which to use: mocks for outbound side effects (email, messaging), stubs for simple data feeds, fakes for repositories and caches where statefulness matters.
How do you structure a test project for an ASP.NET Core solution?
The standard structure is one test project per layer:
ProjectName.UnitTestsโ tests domain logic, application handlers, validatorsProjectName.IntegrationTestsโ tests the HTTP layer withWebApplicationFactory- Shared test utilities (builders, factories, auth helpers) live in a separate
ProjectName.TestHelpersproject referenced by both
Each test project references only the layers it needs to test. The integration test project references the API project (for TProgram). Unit test projects reference the Application or Domain layers only.
What is test isolation and why does it matter?
Test isolation means each test runs independently. A test that passes on its own but fails when run after another test has an isolation problem โ usually shared mutable state in a database or a singleton service.
In ASP.NET Core integration tests, isolation is typically achieved by resetting the database between tests (truncating tables, rolling back transactions, or spinning up a fresh container per test class). Unit tests achieve isolation by replacing all dependencies with controlled test doubles.
Intermediate Questions
How do you test a handler that uses MediatR in an ASP.NET Core application?
Unit-test the handler directly โ instantiate it, inject mock dependencies, call Handle(), and assert the result. You never need MediatR itself in a unit test.
var handler = new GetProductQueryHandler(mockRepo.Object, mockMapper.Object);
var result = await handler.Handle(new GetProductQuery(id: 1), CancellationToken.None);
For integration tests, send commands through the real ISender resolved from the DI container to verify the full pipeline behaviour โ including pipeline behaviours like logging and validation.
How do you handle authentication in integration tests?
Replace the real authentication handler with a test handler that automatically issues a valid ClaimsPrincipal. The common pattern is a custom AuthenticationHandler that reads test claims from a request header, combined with a TestAuthHelper class that creates signed JWTs with configurable roles and claims.
services.AddAuthentication("Test")
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", _ => { });
This avoids real token issuance while still testing authorization policies end-to-end through the real [Authorize] pipeline.
How do you test FluentValidation validators in ASP.NET Core?
Use TestValidate() from FluentValidation.TestHelper. This returns a TestValidationResult that exposes assertion methods:
var result = validator.TestValidate(new CreateProductCommand { Name = "" });
result.ShouldHaveValidationErrorFor(x => x.Name);
Test validators in complete isolation โ no HTTP layer, no DI container. This makes them fast and targeted. Test the happy path, each individual failure case, and cross-field rules separately.
What is IServiceScopeFactory and when do you need it in tests?
IServiceScopeFactory creates a new DI scope. In WebApplicationFactory-based tests, the default HttpClient runs within its own scopes per request. When you need to directly access a scoped service โ such as a DbContext to seed data or verify database state after a request โ you resolve it through a manually created scope:
using var scope = _factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
Without this, resolving scoped services from the root IServiceProvider throws the "Cannot resolve scoped service from root provider" exception โ a common trap in integration test setups.
How do you use Testcontainers in ASP.NET Core integration tests?
Testcontainers spins up real Docker containers (PostgreSQL, SQL Server, Redis) inside your test run. Each test class or collection gets a fresh container, giving you real database behaviour without mocks.
The Testcontainers NuGet package provides fluent builders. The container is started in InitializeAsync and stopped in DisposeAsync. The connection string is injected into the WebApplicationFactory by overriding the database service registration in ConfigureWebHost.
This gives higher confidence than SQLite in-memory (which lacks many SQL Server features) and higher isolation than shared dev databases. It does require Docker available in the CI environment.
How do you test ASP.NET Core middleware?
There are two approaches depending on what you're testing:
Unit approach: Test the middleware class directly by calling InvokeAsync with a mock HttpContext and a stubbed RequestDelegate. This works well for simple request/response transformations.
Integration approach: Use WebApplicationFactory and call the endpoint through the HTTP client. This tests the middleware as part of the real pipeline โ including ordering, short-circuiting, and interaction with other middleware. Prefer this for anything that touches routing, authentication, or response bodies.
Advanced Questions
What is the difference between Moq and NSubstitute, and when would you choose one over the other?
Moq uses a fluent Setup() / Verify() API and lambda expressions. It is the most widely used .NET mocking library and integrates well with existing .NET test ecosystems.
NSubstitute uses a proxy-based API where you call the substituted method directly and then assert against it. Many developers find it more readable, especially for simple stubs.
For most teams, the choice is stylistic. NSubstitute is often preferred for its brevity on assertion-heavy test suites. Moq is preferred when strict verification of call counts and argument matching is important. FakeItEasy is a third option with similar readability to NSubstitute.
The senior answer: pick one, apply it consistently, and document the convention in the project's test guidance file.
How do you test an IHostedService or BackgroundService?
Background services are hard to unit-test through ExecuteAsync directly because they loop and sleep. The better approach is to extract the core business logic into a separate, injectable service (e.g., IJobProcessor) and test that service directly without involving BackgroundService.
For integration tests, start the WebApplicationFactory and let the hosted service run, then assert against observable side effects (database state, published messages). Use Task.Delay with a reasonable timeout and await-until-condition polling to avoid race conditions โ not Thread.Sleep.
For Hangfire or Quartz jobs, test the job class directly by instantiating it with mocked dependencies and calling the execute method.
What does "test pyramid" mean in a senior context, and how do you enforce it on a team?
The test pyramid says: many unit tests at the base, fewer integration tests in the middle, minimal E2E tests at the top. The logic is that lower layers are faster, cheaper to run, and easier to debug.
In practice, teams invert the pyramid by over-relying on integration and E2E tests because they "feel" more real. Senior engineers push back on this by: reviewing test coverage distributions in PRs, setting coverage gates by layer, ensuring that business rules live in domain/application layers testable without HTTP, and writing architecture fitness functions that fail the build when tests of a given type exceed a threshold.
How do you test an ASP.NET Core API endpoint that returns Problem Details?
Assert that the response has the expected HTTP status code, then deserialise the body to ProblemDetails and assert on Status, Title, and Detail. The System.Text.Json serialiser can handle this directly.
var response = await _client.GetAsync("/api/products/99999");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>();
Assert.Equal(404, problem!.Status);
This pattern gives you precise assertion over the error contract โ important for API clients that parse problem details responses.
What are the main risks of using [InlineData] vs [MemberData] vs [ClassData] in xUnit?
[InlineData] is fine for simple, primitive values. It compiles to attribute arguments, so complex objects and collections are not supported.
[MemberData] accepts an IEnumerable<object[]> from a static property or method. It supports complex objects but keeps the data near the test, which can make the test file noisy if the data set is large.
[ClassData] moves the data to a dedicated class implementing IEnumerable<object[]> or TheoryData<T>. This is the cleanest option for reusable or large data sets, and supports strongly-typed TheoryData<T1, T2> which eliminates the object[] boxing.
Senior developers default to [InlineData] for simple cases and [ClassData] with TheoryData for anything complex.
How do you prevent test brittleness when testing ASP.NET Core APIs?
Test brittleness โ tests that break for reasons unrelated to the feature under test โ is a maintenance tax. The main causes and mitigations:
Over-assertion: Asserting the entire response body when only one field matters. Fix: assert only what the test intends to verify.
Order dependency: Tests that rely on database state from previous tests. Fix: reset state between tests using IAsyncLifetime.
Hard-coded values: Dates, IDs, or strings hard-coded in test data that drift over time. Fix: use builders with sane defaults and override only what the test cares about.
Testing implementation details: Asserting which mock method was called instead of what the system produced. Fix: prefer state-based assertions over interaction-based assertions where possible.
Tight coupling to serialisation: Tests that break when a JSON property name changes. Fix: deserialise to a typed model, not string matching against JSON.
How does CancellationToken affect test reliability in ASP.NET Core?
CancellationToken propagation is easy to get right in production but easy to mishandle in tests. The common mistake is passing CancellationToken.None everywhere in tests, which means you never exercise cancellation paths.
For full coverage: write dedicated tests for cancellation behaviour, passing a pre-cancelled CancellationToken or a CancellationTokenSource that cancels after a short delay. Assert that the code handles OperationCanceledException gracefully โ returning the correct response, not leaking resources, and not logging it as an error.
In WebApplicationFactory-based tests, the test HttpClient handles the cancellation token from the HttpRequestMessage automatically.
Observability and Test Quality
What is the role of code coverage in a senior .NET testing strategy?
Code coverage is a useful signal, not a goal. A 90% coverage number tells you which lines were executed โ not whether the tests are meaningful. Teams that chase coverage percentages produce tests that hit code without asserting anything.
The senior approach: use coverage to find gaps, not to award badges. Focus on branch coverage over line coverage (it finds untested conditionals). Make coverage part of the CI pipeline as a floor โ "coverage must not drop below X%" โ not as a metric to maximise. Pair coverage data with mutation testing (Stryker.NET) for a more honest picture of test quality.
What is mutation testing and how does it apply to .NET projects?
Mutation testing tools (Stryker.NET is the .NET standard) introduce small, intentional bugs into the source code โ changing > to >=, removing a return statement โ and then run the test suite against each mutation. If the tests still pass, the mutation "survived," which means the tests did not catch the bug. This reveals test suite weaknesses that coverage cannot.
Stryker generates a mutation score alongside a detailed HTML report. Integrating it into CI (even on a subset of critical modules) gives a far more honest measure of test effectiveness than line coverage alone.
โ Prefer a one-time tip? Buy us a coffee โ every bit helps keep the content coming!
Whether you are preparing for a senior .NET interview or reviewing your team's testing practices, the questions above represent the depth interviewers expect in 2026. The gap between a mid-level and senior answer is usually not knowledge of a framework โ it is the ability to explain the trade-off, name the failure mode, and describe the convention your team would follow. Nail that, and the rest follows.





