...
Todos os testes integrados visto até o presente momento podem ser aplicados no teste da camada de serviço.Recapitulando o que vimos até o presente momento em nossos testes integrados: testamos a nossa camada de aplicação utilizando banco em memoria (Entity Framework Core) e usando uma estrutura de mock (NSubstitute)
O teste desta camada é muito parecido aos testes anteriores de aplicação porque adiciona mais um nível ao teste que é exatamente onde a exposição da camada de aplicação ocorre.
Para testar nossa camada de serviços podemos unir os dois conceitos de testes partindo do principio que nossa aplicação pode ter N fontes de dados (Infraestrutura) e independente de qual sejam elas, precisamos criar testes para validar nossa regra de negocio.
Como nossa camada de serviço expõe toda uma infraestrutura usando fontes como Entity Framework Core e Carol, precisamos utilizar o Entity Framework Core em memoria em conjunto com objetos de mock.
Vamos começar com a criação de Vamos começar com a criação de nosso modulo que fará o carregamento da estrutura dos testes:
...
Note que nosso module ainda contem a mesma estrutura de antes usando a função ReplaceService para substituir o repositorio repositório que será injetado (nesse caso para elucidar não foi usado o NSubstitute, foi criado uma classe concreta, mas nada impede que ele seja usado como nos exemplos anteriores) e configurado o Entity Framework Core para trabalhar em memoria.
...
Como estamos criando um teste para um contexto web uma aplicação ASP .NET Core que expoe nossas APIs, temos que criar uma classe de startup que fará a configuração de nosso pipeline para AspNetCore::
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public class StartupTest
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddApplicationPart(typeof(TnfAspNetCoreModule).GetAssembly())
.AddApplicationPart(typeof(Tnf.Architecture.Web.Startup.WebModule).GetAssembly())
.AddControllersAsServices();
services.AddEntityFrameworkInMemoryDatabase();
// Configure Tnf and Dependency Injection
return services.AddTnf<AppTestModule>(options =>
{
// Test setup
options.SetupTest();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseTnf(); //Initializes Tnf framework.
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
} |
Dessa forma podemos criar um setup para cada teste integrado que for realizado:
No exemplo acima podemos notar algumas coisas que foram configuradas:
AddApplicationPart para TnfAspNetCoreModule e Tnf.Architecture.Web.Startup.WebModule: Esse método informa a configuração do AspNetCore que existem partes da aplicação em outro assembly;AddControllersAsServices: Essa configuração carrega toda nossa estrutura dos assemblies registrados como "AddApplicationPart" e os torna visíveis dentro de nosso cenário de testes;AddEntityFrameworkInMemoryDatabase: Neste ponto configuramos o uso do Entity Framework em memoria para o pipeline do AspNetCore;AddTnf: adicionamos o suporte ao TNF, deixando explicito que estamos criando uma aplicação AspNetCore para testes passando nossa configuração de modulo (AppTestModule definido anteriormente);Com nossa classe de startup criada podemos definir a classe de setup de cada teste integrado usando AspNetCore, Entity Framework em memoria e objetos mock:
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public abstract class AppTestBase : TnfAspNetCoreIntegratedTestBase<StartupTest>
{
protected override void InitializeIntegratedTest()
{
IocManager.UsingDbContext<ArchitectureDbContext>(
context => | ||||||||
| Bloco de código | ||||||||
| ||||||||
public abstract class AppTestBase : TnfAspNetCoreIntegratedTestBase<StartupTest> { protected override void InitializeIntegratedTest() { IocManager.UsingDbContext<ArchitectureDbContext>( context => { context.Countries.Add(new Country(1, "Brasil")); context.Countries.Add(new Country(2, "EUA")); context.Countries.Add(new Country(3, "Uruguai")); context.Countries.Add(new Country(4, "Paraguai")); { context.Countries.Add(new Country(51, "VenezuelaBrasil")); }); } } |
Note que aqui usamos para cada teste a herança da classe que o TNF disponibiliza chamada TnfAspNetCoreIntegratedTestBase. Essa classe foi criada para auxiliar em cenários de teste como esse onde precisamos testas um contexto AspNetCore.
Essa classe prove algumas facilitadores para realizar chamadas para nossa aplicação web de testes.
context.Countries.Add(new Country(2, "EUA"));
context.Countries.Add(new Country(3, "Uruguai"));
context.Countries.Add(new Country(4, "Paraguai"));
context.Countries.Add(new Country(5, "Venezuela"));
});
}
} |
Note que aqui usamos para cada teste a herança da classe TnfAspNetCoreIntegratedTestBase<Startup>.
Essa classe auxilia em cenários de teste como esse onde precisamos testar um contexto AspNetCore, recebendo como parâmetro genérico a classe de startup com as configurações da aplicação de teste.
Essa classe contém um método que pode ser sobrescrito chamado "InitializeIntegratedTest", onde é possível realizar a configuração (setup) de cada teste executado.
No exemplo acima vemos que através do objeto IocManager conseguimos acessar o método UsingDbContext<DbContext> que recebe como parâmetro genérico o DbContext, para inserir nossos dados em memória.
Os objetos de mock foram configurados dentro do nosso modulo através de uma implementação concretra, mas como visto em testes anteriores podemos fazer o uso do NSubstitute.
Vamos agora definir uma classe de teste e realizar o teste em cima de nossa API:
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public class WhiteHouseControllerTests : AppTestBase
{
[Fact]
public void Should_Resolve_Controller()
{
ServiceProvider.GetService<WhiteHouseController>().ShouldNotBeNull();
}
[Fact]
public async Task GetAll_Presidents_With_Success()
{
// Act
var response = await GetResponseAsObjectAsync<AjaxResponse<PagingDtoResponse<PresidentDto>>>(
"/api/white-house?offset=0&pageSize=10",
HttpStatusCode.OK
);
// Assert
Assert.True(response.Success);
Assert.Equal(response.Result.Data.Count, 6);
response.Result.Notifications.ShouldBeEmpty();
}
[Fact]
public async Task GetAll_Presidents_With_Invalid_Parameters()
{
// Act
var response = await GetResponseAsObjectAsync<AjaxResponse<string>>(
"/api/white-house?offset=0&pageSize=0",
HttpStatusCode.BadRequest
);
response.Success.ShouldBeTrue();
response.Result.ShouldBe($"Invalid parameter: pageSize");
}
} |
Em nossa classe de teste temos acesso a alguns métodos pela herança da classe TnfAspNetCoreIntegratedTestBase contida dentro do pacote Tnf.AspNetCore.TestBase para criação de testes em serviços.
Entre os métodos disponíveis podemos listar:
Esses métodos realizam a conversão da resposta para o formato esperado, onde é informado a url e o status Http de retorno esperado.
Em conjunto a isso nos testes acima foram usados o Xunit e o Shouldly, adicionando ainda mais suporte na hora de realizar as asserções em nossos retornos.
Neste teste temos o nível de integração máximo de nossa arquitetura, integrando as camadas de serviço, aplicação, domínio e infraestrutura.