Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add page regarding testing ASP.NET Core applications #4

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@
## Advanced topics

* [Testing HttpClient](advanced-topics/testing-httpclient.md)
* [Testing ASP.NET Core applications](advanced-topics/testing-aspnetcore-apps.md)
95 changes: 95 additions & 0 deletions docs/advanced-topics/testing-aspnetcore-apps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
ASP.NET Core web applications are tricky to test with classic unit tests.

While it is still possible to create fixtures that target controllers or page models, there are aspects of the web application that are difficult to mock like routing and URL generation.

Rather than creating complex setups to mimic the behavior of the web framework, it's possible to leverage the `WebApplicationFactory<TEntryPoint>` to create an instance of the application in the same address space of the test runner. Unit tests will then be able to access the web application via a `HttpClient` configured ad-hoc.

## Simple example

Here is a test fixture that tests a web application created based on the built-in Web API template.

```csharp
public class Tests
{
private HttpClient client;

[SetUp]
public void Setup()
{
var factory = new WebApplicationFactory<Startup>();

client = factory.CreateClient();
}

[Test]
public async Task Get_WeatherForecast_returns_forecasts()
{
var response = await client.GetFromJsonAsync<WeatherForecast[]>("/WeatherForecast");

Assert.That(response, Has.Exactly(5).InstanceOf<WeatherForecast>());
}
}
```

The `WebApplicationFactory` is part of the `Microsoft.AspNetCore.Mvc.Testing` package.

## Customizing the application

In the likely case the controller under test has dependencies, the `WebApplicationFactory` can be customized with a builder delegate that allows the customization of aspects of the application setup like service registration.

Here is a version of the `WebApplicationFactory` where a service is overridden with a fake.

```csharp
var factory = new WebApplicationFactory<Startup>().WithWebHostBuilder((IWebHostBuilder builder) =>
{
builder.ConfigureServices(services =>
{
services.AddSingleton<IWeatherService, MyFakeWeatherService>();
});
});
```

Through the given `builder`, it is possible to customize the application configuration via the `ConfigureAppConfiguration` method, the logging pipeline via the `ConfigureLogging` method and all other aspects of the web application.

## Leveraging a mocking framework

Alternatively, it is possible to use mocking libraries like `Moq` to provide a dynamically generated mock of the service interface.

```csharp
public class Tests
{
private HttpClient client;
private Mock<IWeatherService> _weatherService;

[SetUp]
public void Setup()
{
_weatherService = new Mock<IWeatherService>();

var factory = new WebApplicationFactory<Startup>().WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
services.AddSingleton<IWeatherService>(_weatherService.Object);
});
});

client = factory.CreateClient();
}

[Test]
public async Task Get_WeatherForecast_returns_forecasts_from_service()
{
var result = new []
{
new WeatherForecast { /* ... */ }
};

_weatherService.Setup(p => p.GetAll()).Returns(result);

var response = await client.GetFromJsonAsync<WeatherForecast[]>("/WeatherForecast");

Assert.That(response, Has.Exactly(result.Length).InstanceOf<WeatherForecast>());
}
}
```