| Índice |
|---|
Testes de integração compreendem a segunda fase do ciclo de testes de uma aplicação.
Após testar cada unidade o próximo passo é testar um grupo de funcionalidades, como um todo, fazendo a integração de todos os módulos da aplicação.
...
...
...
Se você construiu uma aplicação onde tenha utilizado uma infraestrutura com Entity Framework Core podemos utilizar um banco em memoria para realizar nossos testes integrados.
Vamos começar com a criação do modulo que fará o carregamento da estrutura de testes:
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
[DependsOn(
typeof(AppModule),
typeof(TnfTestBaseModuleTnfAppTestBaseModule))]
public class EfCoreAppTestModule : TnfModule
{
public override void PreInitialize()
{
Configuration.Modules
.TnfEfCoreInMemory(IocManager.IocContainer)
.RegisterDbContextInMemory<ArchitectureDbContext>();
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
} |
O modulo "EfCoreAppTestModule" foi criado no projeto de testes sendo responsável por carregar toda a estrutura de sua aplicação para o teste integrado .quando você trabalhar com testes e Entity Framework em memoria.
Note Note que o atributo do modulo "DependsOn" tem como referencia outro modulo chamado "AppModule". Este modulo é um modulo da camada de aplicação concreta a ser testada, contendo dependências de outras camadas como domínio e infraestrutura onde as entidades estão sendo persistidas com o Entity Framework Core.
Também temos como dependência o modulo "TnfTestBaseModuleTnfAppTestBaseModule" do TNF carregando toda a estrutura de testes integrados.
O código exemplificado acima, carrega o modulo da camada a ser testada, configurando no método "PreInitialize" o uso do Entity Framework Core em memoria para o DbContext "ArchitectureDbContext" através do método "RegisterDbContextInMemory".Após a definição de nosso módulo
Definido o modulo de teste vamos criar a classe de Setup para cada teste integrado usando Entity Framework Core em memoria.Para isso a classe abaixo nomeada de nosso classe de setup chamada "EfCoreAppTestBase" realiza realizando a herança da classe "TnfEfCoreIntegratedTestBase<Module>" que recebe um TnfModule (implementado anteriormente):
...
Essa classe força a implementação do método "InitializeIntegratedTest" onde será definido o setup de dados.
nosso contexto do Entity Framework Core em memoria. A mesma classe expõe alguns métodos para inclusão de dados na memoria do contexto como podemos observar no exemplo acima usando a instrução "UsingDbContext<DbContext>".
Definida a classe que fará o setup de cada teste criado, podemos de setup, vamos começar a escrita de nossos testes sobre os serviços de aplicação:
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public class CountryAppServiceTests : EfCoreAppTestBase
{
private readonly ICountryAppService _countryAppService;
public CountryAppServiceTests()
{
_countryAppService = LocalIocManager.Resolve<ICountryAppService>();
}
[Fact]
public void Service_Should_Not_Be_Null()
{
_countryAppService.ShouldNotBeNull();
}
[Fact]
public async Task Create_Item_With_Sucess()
{
var result = await _countryAppService.Create(new CountryDto()
{
Id = 6,
Name = "Mexico"
});
result.Name.ShouldBe("Mexico");
}
[Fact]
public async Task Get_Return_Item_With_Sucess()
{
var result = await _countryAppService.Get(new EntityDto<int>(1));
result.Id.ShouldBe(1);
result.Name.ShouldBe("Brasil");
}
} |
Note que no exemplo acima Acima realizamos a herança da nossa classe que realiza o setup dos dados em nosso contexto em memoria. Agora podemos Podemos agora executar nossos testes em nosso serviço de aplicação normalmente em cima da estrutura em memoria do Entity Framework Core.
Cada teste executado no cenário acima será de forma reproduzido de forma isolada.
Cada Todo teste executado contém seu próprio contexto do Entity Framework Core rodando separadamente dos demais.
...
Quando estamos trabalhando com repositórios sem o uso de um ORM, precisamos fazer o mock da interface do repositório simulando repositório, simulando um cenário afim de explorar todo o funcionamento do sistema.
Em nosso caso vamos teste funcionalidades usando testar a infraestrutura do Carol de nosso cenário de testes.
Para isso o TNF possui uma extensão onde é possível realizar a substituição de uma injeção e usar usando o NSubstitute para criar objeto de mock no injetor de dependência.
...
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
[DependsOn(
typeof(AppModule),
typeof(TnfTestBaseModuleTnfAppTestBaseModule))]
public class NSubstituteAppTestModule : TnfModule
{
public override void PreInitialize()
{
// Mock repositories
Configuration.ReplaceService<IWhiteHouseRepository>(() =>
{
var instance = Substitute.For<IWhiteHouseRepository>();
var presidentToInsert = new PresidentDto("1", "New President", "55833479");
instance.InsertPresidentsAsync(Arg.Any<List<PresidentDto>>)
.Returns(Task.FromResult(presidentToInsert));
IocManager.IocContainer.Register(
Component
.For<IWhiteHouseRepository>()
.Instance(instance)
.LifestyleTransient()
);
});
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
} |
O modulo "NSubstituteAppTestModule" foi criado no projeto de testes sendo responsável por para carregar toda a estrutura de sua aplicação para o teste integrado.
Note que o atributo do modulo "DependsOn" tem como referencia outro modulo chamado "AppModule". Este modulo é um modulo da , carregando a camada de aplicação concreta a ser testada. Esse modulo também contém dependências de outras camadas como domínio e infraestrutura onde estão definidos nossos repositórios de dados.
Também temos como dependência o modulo "TnfTestBaseModule" do TNF carregando toda TnfAppTestBaseModule" para carregar a estrutura de testes integrados.
O código exemplificado acima, carrega o modulo da camada a ser testada, configurando no método "PreInitialize" com os objetos de mock. No nosso exemplo estamos mocando através da função ReplaceService do TNF usando o framework NSubstitute para criar um objeto de mock para esse repositório e registrá-lo no container de injeção de dependência.
Após a definição de nosso do módulo vamos criar a classe de Setup para cada teste integrado.Para : para isso a classe abaixo nomeada de "NSubstituteAppTestBase" realiza a herança da classe "TnfEfCoreIntegratedTestBase<Module>TnfAppIntegratedTestBase<Module>" que recebe um TnfModule (implementado anteriormente):
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public class NSubstituteAppTestBase : TnfIntegratedTestBase<NSubstituteAppTestModule>TnfAppIntegratedTestBase<NSubstituteAppTestModule> { } |
A classe TnfIntegratedTestBase está TnfAppIntegratedTestBase está contida no pacote Tnf.App.TestBase.
Definido nosso setup podemos implementar nossa classe e realizar os testes usando nosso repositório criado com NSubstitute.
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public class WriteHouseAppServiceTests : NSubstituteAppTestBase
{
private readonly IWhiteHouseAppService _whiteHouseAppService;
public WriteHouseAppServiceTests()
{
_whiteHouseAppService = LocalIocManager.Resolve<IWhiteHouseAppService>();
}
[Fact]
public async Task Should_Insert_President_With_Success()
{
// Act
var response = await _whiteHouseAppService.InsertPresidentAsync(new PresidentDto("1", "New President", "12345678"));
// Assert
Assert.TrueFalse(responseLocalNotification.SuccessHasNotification());
}
} |
Podemos perceber que toda a estrutura de Injeção de dependência ainda é mantida inclusive nos testes.
O objeto LocalIocManager contém um wrapper que pode ser acessado para resolver toda a dependencia dependência das camadas carregadas em nosso teste através de nosso modulo que foi definido.
Com o NSubstitute criamos o objeto e então fazemos fazendo sua substituição dentro da estrutura de IoC do framework, possibilitando assim consumir nosso mock.
Assim temos o teste simulando toda a pilha de dependências do teste integrado. Cada teste executado no cenário acima será de forma reproduzido de forma isolada.
...
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
[DependsOn(
typeof(AppModule),
typeof(TnfAspNetCoreTestBaseModuleTnfAppAspNetCoreTestBaseModule))]
public class AppTestModule : TnfModule
{
public override void PreInitialize()
{
Configuration.Auditing.IsEnabledForAnonymousUsers = true;
// Mock repositories
Configuration.ReplaceService<IWhiteHouseRepository, WhiteHouseRepositoryMock>();
Configuration.Modules
.TnfEfCoreInMemory(IocManager.IocContainer, IocManager.Resolve<IServiceProvider>())
.RegisterDbContextInMemory<ArchitectureDbContext>();
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(AppTestModule).GetAssembly());
}
}
} |
...
Nosso modulo agora contem dependências diferente pois vamos testar uma camada de AspNetCore e por isso devemos deixar explicito em nosso atributo "DependsOn" o uso do modulo TnfAspNetCoreTestBaseModuleTnfAppAspNetCoreTestBaseModule.
Como estamos criando um teste para uma aplicação ASP .NET Core que expoe expõe nossas APIs, temos que criar uma classe de startup que fará a configuração de nosso pipeline:
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public class StartupTest
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddApplicationPartAddMvcCore(typeof(TnfAspNetCoreModule).GetAssembly())
.AddApplicationPart(typeof(Tnf.Architecture.Web.Startup.WebModule).GetAssembly())
.AddControllersAsServices();
services.AddEntityFrameworkInMemoryDatabase();
// Configure Tnf and Dependency Injection
return services.AddTnf<AppTestModule>AddTnfApp<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?}");
});
}
} |
No exemplo acima podemos notar algumas coisas que foram configuradas:
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
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 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>>>GetResponseAsObjectAsync<ListDto<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" $"{RouteConsts.WhiteHouse}?pageSize=5",
HttpStatusCode.OK
);
// Assert
Assert.Equal(response.Items.Count, 5);
}
[Fact]
public async Task GetAll_Presidents_With_Invalid_Parameters_Return_Bad_Request()
{
// Act
var response = await GetResponseAsObjectAsync<ErrorResponse>(
$"{RouteConsts.WhiteHouse}",
HttpStatusCode.BadRequest
);
// Assert
response.Message.ShouldBe("GetAllPresident");
response.DetailedMessage.ShouldBe("GetAllPresident");
Assert.True(response.Details.Any(n => n.Message == Error.InvalidParameter.ToString()));
}
} |
Em nossa classe de teste temos acesso a alguns métodos pela herança da classe TnfAspNetCoreIntegratedTestBase contida dentro do pacote Tnf.App.AspNetCore.TestBase para criação de testes em serviços.
...