Mockando Injeção de Dependência em Testes Integrados no ASP.NET 3.1

alsouza93

Alessandra Souza

Posted on October 8, 2020

Mockando Injeção de Dependência em Testes Integrados no ASP.NET 3.1

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
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

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

var hostBuilder = new HostBuilder()
            .ConfigureWebHost(webHost =>
            {                      
                webHost.UseTestServer();
                webHost.UseEnvironment("Test");
                webHost.UseStartup<Startup>();
            });
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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)]
        };

    }
}
Enter fullscreen mode Exit fullscreen mode

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>();
     });
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Caso queira consultar o código utilizado neste artigo, é só acessar o link do repositório no GitHub.

Referências

Este artigo foi fortemente baseado em uma série de artigos do Adam Storr, 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.

Por fim não deixe de ler a documentação do ASP.NET Core.

💖 💪 🙅 🚩
alsouza93
Alessandra Souza

Posted on October 8, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related