Mockando Injeção de Dependência em Testes Integrados no ASP.NET 3.1
Tags: csharp, testing, aspnetcor
Published: 2020-10-08 17:10:30

#### O que é um Teste de Integração?

O teste de integração é o teste entre diferentes módulos em um sistema, testando a integração entre as unidades. Muito útil para testar *middlewares*, mas além disso é possível testar uma requisição completa, evitando falhas como as citadas abaixo:

**Erros de interpretação:** ocorre quando uma funcionalidade está implementada diferente da especificação.
**Chamadas incorretas:** quando existe a chamada de unidades em locais errados.
**Erros de interface:** quando o padrão de integração entre as unidades foi implementado erroneamente, seja a ordem, tipo ou formato de parâmetros errados.

Para este tutorial, foram feitas algumas modificações na API que o próprio modelo ASP.NET Core Web Application cria ao inicializar um novo projeto.

```
 [ApiController]
public class WeatherForecastController : ControllerBase
{
	private readonly IWeatherService _weatherService;
	public WeatherForecastController(IWeatherService weatherService)
	{
		_weatherService = weatherService;
	}

	[HttpGet("/report")]
	public ContentResult GetWeatherForecastReport()
	{
		return new ContentResult()
		{
			Content = _weatherService.WriteWeatherForecastReport(),
			StatusCode = 200
		};
	}
}
```
Criei uma camada de serviço e repositório para obter a previsão do tempo ao invés de deixar este código na controller. 

Agora iremos partir para o teste de integração!

#### Criação de um Teste de Integração 

Para criar um teste de integração é necessário criar um host e para isso utilizaremos o HostBuilder. Para saber mais sobre host e as mudanças do Asp.Net Core 3.1 para as versões anteriores, eu sugiro a leitura deste artigo. [Link](https://stackoverflow.com/questions/52085806/what-is-the-difference-between-iwebhost-webhostbuilder-buildwebhost)

```
var hostBuilder = new HostBuilder()
			.ConfigureWebHost(webHost =>
			{                      
				webHost.UseTestServer();
				webHost.UseEnvironment("Test");
				webHost.UseStartup<Startup>();
			});
```
O *UseTestServer* indica que iremos utilizar o *TestServer*.
> Nota: O *UseTestServer* está dentro do pacote Microsoft.AspNetCore.Mvc.Testing.

Em UseEnvironment indicamos qual ambiente iremos utilizar para realizar o teste.

Por último informamos qual classe Startup iremos utilizar.

Após construir o host, precisamos inicializá-lo, e para isso utilizamos o código abaixo:

`var host = hostBuilder.StartAsync();`

Agora é necessário criar uma instância para este servidor em memória que acabamos de criar.

`var client = host.GetTestClient();`

Por fim, fazemos a chamada do endpoint.

`var response =  client.GetAsync("/report");`

Vamos ver agora como ficou o teste completo:
```
[Fact]
public void TestReport()
{
	//Arrange
	var hostBuilder = new HostBuilder()
			.ConfigureWebHost(webHost =>
			{                      
				webHost.UseTestServer();
				webHost.UseEnvironment("Test");
				webHost.UseStartup<Startup>();
			});

	var host = hostBuilder.Start();
	var client = host.GetTestClient();

	// Act
	var response =  client.GetAsync("/report");
	var responseString = response.Result.Content.ReadAsStringAsync();

	// Assert
	Assert.NotNull(responseString);
}
```
#### Teste de Integração Trocando a Injeção de Dependência

Agora que você já sabe a estrutura básica de um teste de integração no Asp.Net Core 3.1, vamos aprender como trocar a injeção de dependência.

No cenário anterior, temos a controller com uma injeção de dependência para o serviço e o serviço dependia de uma interface do tipo IWeatherRepository.
```
public class WeatherRepository : IWeatherRepository
{
	private static readonly string[] Summaries = new[]
	{
		"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
	};

	public WeatherForecast GetWeatherForecast()
	{
		var rng = new Random();
		return  new WeatherForecast
		{
			Date = DateTime.Now,
			TemperatureC = rng.Next(-20, 55),
			Summary = Summaries[rng.Next(Summaries.Length)]
		};
		
	}
}
```
O repositório simplesmente devolvia uma previsão do tempo randômica, mas e se quisermos trocar por um repositório fake que esteja no projeto de teste para sempre nos devolver uma mesma previsão do tempo? 

Para isso será necessário adicionar uma configuração a mais no construtor do host. 

Na construção do host, temos acesso ao método ConfigureTestServices. Caso ele seja adicionado depois do UseStartup, nós podemos acessar o IServiceCollection que foi gerado anteriormente para criar um provedor de serviços.

```
webHost.ConfigureTestServices(services =>
	 {
		 services.SwapTransient<IWeatherRepository, FakeWeatherRepository>();
	 });
```
Com o método abaixo, podemos buscar qual foi a implementação configurada na Startup para a interface, e realizar a troca pela implementação que quisermos.

```
public static void SwapTransient<TService, TImplementation>(this IServiceCollection services)
where TImplementation : class, TService
{
	if (services.Any(x => x.ServiceType == typeof(TService) && x.Lifetime == ServiceLifetime.Transient))
	{
		var serviceDescriptors = services.Where(x => x.ServiceType == typeof(TService) &&
		x.Lifetime == ServiceLifetime.Transient).ToList();
		foreach (var serviceDescriptor in serviceDescriptors)
		{
			services.Remove(serviceDescriptor);
		}
	}

	services.AddTransient(typeof(TService), typeof(TImplementation));
}
```

O método de teste ficou assim:
```
[Fact]
public void TestReportWithFixForecast()
{
	//Arrange
	var hostBuilder = new HostBuilder()
		 .ConfigureWebHost(webHost =>
		 {
			 webHost.UseTestServer();
			 webHost.UseEnvironment("Test");
			 webHost.UseStartup<Startup>();
			 webHost.ConfigureTestServices(services =>
			 {
				 services.SwapTransient<IWeatherRepository, FakeWeatherRepository>();
			 });
		 });

	var host = hostBuilder.Start();
	var client = host.GetTestClient();

	// Act
	var response = client.GetAsync("/report");
	var responseString = response.Result.Content.ReadAsStringAsync();

	// Assert
	Assert.Equal("Today's weather is Warm. With temperature 73", responseString.Result);
}
```

Caso queira consultar o código utilizado neste artigo, é só acessar o link do repositório no [GitHub](https://github.com/alsouza93/IntegrationTestApi.git).

#### Referências

Este artigo foi fortemente baseado em uma série de artigos do [Adam Storr](https://adamstorr.azurewebsites.net/blog/integration-testing-with-aspnetcore-3-1), indico a leitura caso tenha interesse em se aprofundar mais no assunto.

Caso queira saber mais sobre testes de integração indico a leitura desta [tese](https://teses.usp.br/teses/disponiveis/55/55134/tde-01082017-155344/publico/MariaAdelinaSilvaBrito_revisada.pdf).

Por fim não deixe de ler a documentação do [ASP.NET Core] (https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1).