...
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.
Para este tipo de teste o TNF traz algumas classes e definições na hora da criação do seu teste integrado.
As estruturas de asserts dos testes descritos abaixo foram criados usando o Xunit e o Shouldly.
Testes na camada de aplicação
Para criar testes dessa camada temos que analisar o que sua aplicação utiliza como infraestrutura.
Considere para os cenários a seguir a seguinte tabela criada no Entity Framework Core e seu DTO
Considere a estrutura de testes apresentada no capitulo anterior: Testes#Cenáriodetestes
Vamos começar criando os projetos onde estarão nossos testes integrados:
Em nosso visual studio: File -> New -> Project e vamos escolher a opção Class Library (.NET Core)
Para utilizar os frameworks de teste instale os seguintes pacotes via nuget:
Em nosso visual studio: File -> New -> Project e vamos escolher a opção Class Library (.NET Core)
Para utilizar os frameworks de teste instale os seguintes pacotes via nuget:
Para adicionar pacotes do TNF adicione nos sources do nuget o endereço: https://www.myget.org/F/tnf/api/v3/index.json
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(TnfTestBaseModule))]
public class EfCoreAppTestModule : TnfModule
{
public override void PreInitialize( | ||||||||
| Bloco de código | ||||||||
| ||||||||
[AutoMap(typeof(CountryDto))] [Table("Countries")] public class Country : Entity { public const int MaxNameLength = 256; [Required] [MaxLength(MaxNameLength)] public string Name { get; set; } public Country() { } public Country(int id, string name) { IdConfiguration.Modules = id; .TnfEfCoreInMemory(IocManager.IocContainer) Name = name; } } public class CountryDto { public string Name { get; set; } } |
...
.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.
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 "TnfTestBaseModule" 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 em memoria para o DbContext "ArchitectureDbContext" através do método "RegisterDbContextInMemory".
Após a definição de nosso módulo vamos criar a classe de Setup para cada teste integrado usando Entity Framework Core em memoria.
Para isso a classe abaixo nomeada de "EfCoreAppTestBase" realiza a herança da classe "TnfEfCoreIntegratedTestBase<Module>" que recebe um TnfModule (implementado anteriormente):
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public class EfCoreAppTestBase : TnfEfCoreIntegratedTestBase<EfCoreAppTestModule>
{
protected override void InitializeIntegratedTest()
{
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(5, "Venezuela"));
});
}
} |
A classe TnfEfCoreIntegratedTestBase está contida no pacote Tnf.App.EntityFrameworkCore.TestBase.
Essa classe força a implementação do método "InitializeIntegratedTest" onde será definido o setup de 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 começar a escrita de nossos testes sobre os serviços de aplicação:
| Bloco de código | ||||||
|---|---|---|---|---|---|---|
|
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(TnfTestBaseModule))]
public class AppTestModule : TnfModule
{
public override void PreInitialize()
{
Configuration.Modules
.TnfEfCoreInMemory(IocManager.IocContainer)
.RegisterDbContextInMemory<ArchitectureDbContext>();
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
} |
O modulo "AppTestModule" foi criado no projeto de testes sendo responsável por 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 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 as entidades estão sendo persistidas com o Entity Framework Core.
O código exemplificado acima, carrega o modulo da camada a ser testada, configurando no método "PreInitialize" o uso do Entity Framework em memoria para o DbContext "ArchitectureDbContext". Essa configuração irá habilitar o uso da estrutura em memoria podendo assim criar um setup em cada teste criado onde será possível incluir a estrutura em memoria no ORM (setup) executando assim o teste em cima da camada de serviço.
Para criação do Setup de cada teste o TNF disponibiliza uma classe abstrata chamada "TnfEfCoreIntegratedTestBase<Module>" que recebe um TnfModule ("AppTestModule" vide exemplo anterior):
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public class AppTestBaseCountryAppServiceTests : TnfEfCoreIntegratedTestBase<AppTestModule>EfCoreAppTestBase { protectedprivate overridereadonly void InitializeIntegratedTest()ICountryAppService _countryAppService; { UsingDbContext<ArchitectureDbContext>(public CountryAppServiceTests() { context_countryAppService => LocalIocManager.Resolve<ICountryAppService>(); } [Fact] public void Service_Should_Not_Be_Null() { context.Countries.Add(new Country(1, "Brasil") _countryAppService.ShouldNotBeNull(); } [Fact] public async Task Create_Item_With_Sucess() { context.Countries.Add var result = await _countryAppService.Create(new Country(2, "EUA")); CountryDto() { context.Countries.Add(new Country(3, "Uruguai")); Id = 6, Name context.Countries.Add(new Country(4, "Paraguai")); = "Mexico" }); result.Name.ShouldBe("Mexico"); } [Fact] public async Task Get_Return_Item_With_Sucess() { var result = await context.Countries.Add_countryAppService.Get(new Country(5, "Venezuela"EntityDto<int>(1)); result.Id.ShouldBe(1); }result.Name.ShouldBe("Brasil"); } } |
A classe TnfEfCoreIntegratedTestBase está contida no pacote Tnf.App.EntityFrameworkCore.TestBase em nosso feed: "https://www.myget.org/F/tnf/api/v3/index.json"
Essa classe força a implementação do método "InitializeIntegratedTest" onde será definido o setup de 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 o do exemplo acima "UsingDbContext<DbContext>".
Definida a classe que fará o setup de cada teste criado, podemos começar a escrita de nossos testes sobre os serviços de aplicação:
...
| language | c# |
|---|---|
| firstline | 1 |
| title | ICountryAppService.cs |
| linenumbers | true |
...
Note que no exemplo acima realizamos a herança da nossa classe que realiza o setup dos dados em nosso contexto em memoria. Agora podemos 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 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 um cenário afim de explorar todo o funcionamento do sistema.
Em nosso caso vamos teste funcionalidades usando 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 o NSubstitute para criar objeto de mock no injetor de dependência.
Vamos criar o modulo que irá carregar a estrutura de nosso teste e fazer a substituição do serviço no injetor de dependência.
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
[DependsOn( typeof(AppModule), typeof(TnfTestBaseModule))] public class CountryAppServiceNSubstituteAppTestModule : AsyncCrudAppService<Country, CountryDto>, ICountryAppServiceTnfModule { public CountryAppService(IRepository<Country> repository) : base(repositoryoverride void PreInitialize() { // Mock repositories } } |
O serviço de aplicação do exemplo acima utiliza o AsyncCrudAppService do TNF que cria a estrutura de CRUD automaticamente.
Após criar nosso serviço de aplicação, podemos começar o desenvolvimento dos testes:
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public class CountryAppServiceTests : AppTestBase { private readonly ICountryAppService _countryAppService; Configuration.ReplaceService<IWhiteHouseRepository>(() => public CountryAppServiceTests(){ { _countryAppServicevar instance = LocalIocManagerSubstitute.Resolve<ICountryAppService>For<IWhiteHouseRepository>(); } [Fact] public void Service_Should_Not_Be_Null() { _countryAppService.ShouldNotBeNull(); } [Fact] public async Task Create_Item_With_Sucess() {var presidentToInsert = new PresidentDto("1", "New President", "55833479"); var result = await _countryAppService.Create(new CountryDto() { instance.InsertPresidentsAsync(Arg.Any<List<PresidentDto>>) .Returns(Task.FromResult(presidentToInsert)); IocManager.IocContainer.Register( IdComponent = 6, Name = "Mexico" }); result.Name.ShouldBe("Mexico"); } [Fact] .For<IWhiteHouseRepository>() public async Task Get_Return_Item_With_Sucess() { var result = await _countryAppService.Get(new EntityDto<int>(1)); Instance(instance) result.Id.ShouldBe(1); .LifestyleTransient() ); }); } public override result.Name.ShouldBe("Brasil"void Initialize() { IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); } } |
Note que no exemplo acima realizamos a herança da nossa classe que realiza o setup dos dados em nosso contexto em memoria. Agora podemos 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.
Quando estamos trabalhando com repositórios sem o uso de um ORM, precisamos fazer o mock da interface do repositório simulando um cenário afim de explorar todo o funcionamento do sistema.
Para isso o TNF possui uma extensão onde é possível realizar a substituição de uma injeção e usar o NSubstitute para criar objeto de mock no injetor de dependência.
Para o seguinte cenário de testes que iremos construir considere a seguinte entidade e Dto:
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
[AutoMap(typeof(PresidentDto))]
public class President : Entity<string>
{
public const int MaxNameLength = 256;
public string Name { get; internal set; }
public ZipCode ZipCode { get; internal set; }
public enum Error
{
Unexpected = 0,
InvalidId = 1,
PresidentNameMustHaveValue = 2,
PresidentZipCodeMustHaveValue = 3,
}
}
public class PresidentDto
{
public PresidentDto()
{
}
public PresidentDto(string id, string name, string zipCode)
{
Id = id;
Name = name;
ZipCode = new ZipCode(zipCode);
}
public string Id { get; set; }
public string Name { get; set; }
public ZipCode ZipCode { get; set; }
} |
Para realizar teste em cenários com o uso mocks o TNF prove alguns facilitadores.
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(TnfTestBaseModule))]
public class AppTestModule : TnfModule
{
public override void PreInitialize()
{
// Mock repositories
Configuration.ReplaceService<IWhiteHouseRepository>(() =>
{
var instance = Substitute.For<IWhiteHouseRepository>();
var presidentsToInsert = new List<PresidentDto>()
{
new PresidentDto("1", "New President", "55833479")
};
instance.InsertPresidentsAsync(Arg.Any<List<PresidentDto>>)
.Returns(Task.FromResult(presidentsToInsert));
IocManager.IocContainer.Register(
Component
.For<IWhiteHouseRepository>()
.Instance(instance)
.LifestyleTransient()
);
});
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
} |
O modulo "AppTestModule" foi criado no projeto de testes sendo responsável por 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 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.
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.
Para criação do Setup de cada teste o TNF disponibiliza uma classe abstrata chamada "TnfIntegratedTestBase<Module>" que recebe um TnfModule ("AppTestModule"):
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public class AppTestBase : TnfIntegratedTestBase<AppTestModule>
{
} |
A classe TnfIntegratedTestBase está contida no pacote Tnf.TestBase em nosso feed: "https://www.myget.org/F/tnf/api/v3/index.json"
Definida a classe que fará o setup de cada teste criado, podemos começar a escrita de nossos testes sobre os serviços de aplicação:
| Bloco de código | ||||||
|---|---|---|---|---|---|---|
| ||||||
public interface IWhiteHouseAppService : IApplicationService
{
Task<DtoResponseBase<PresidentDto>> InsertPresidentAsync(PresidentDto dto);
} |
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public class WhiteHouseAppService : ApplicationService, IWhiteHouseAppService
{
private readonly IWhiteHouseService _whiteHouserService;
public WhiteHouseAppService(IWhiteHouseService whiteHouserService)
{
_whiteHouserService = whiteHouserService;
}
public async Task<DtoResponseBase<PresidentDto>> InsertPresidentAsync(PresidentDto dto)
{
var response = await _whiteHouserService.InsertPresidentAsync(dto);
return response;
}
} |
O modulo "NSubstituteAppTestModule" foi criado no projeto de testes sendo responsável por 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 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 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 módulo vamos criar a classe de Setup para cada teste integrado.
Para isso a classe abaixo nomeada de "NSubstituteAppTestBase" realiza a herança da classe "TnfEfCoreIntegratedTestBase<Module>" que recebe um TnfModule (implementado anteriormente)Os serviços de aplicação consomem via injeção de dependência os serviços de domínio executando as validações presentes no builder da entidade:
| Bloco de código | ||||||||
|---|---|---|---|---|---|---|---|---|
| ||||||||
public interfaceclass IWhiteHouseServiceNSubstituteAppTestBase : IDomainService { Task<DtoResponseBase<PresidentDto>> InsertPresidentAsync(PresidentDto president); }TnfIntegratedTestBase<NSubstituteAppTestModule> { } |
A classe TnfIntegratedTestBase está contida no pacote Tnf.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(public class WhiteHouseService : DomainService<IWhiteHouseRepository>, IWhiteHouseService { public WhiteHouseService(IWhiteHouseRepository repository) : base(repository) { } public async Task<DtoResponseBase<PresidentDto>> InsertPresidentAsync(PresidentDto president) { var response = new DtoResponseBase<PresidentDto>(); var builder = new PresidentBuilder() .WithId(item.Id) .WithName(item.Name) { .WithZipCode(item.ZipCode); var build _whiteHouseAppService = builderLocalIocManager.BuildResolve<IWhiteHouseAppService>(); if (!build.Success)} [Fact] public response.AddNotifications(build.Notifications); if (response.Success) async Task Should_Insert_President_With_Success() { // {Act var presidentresponse = await Repository.InsertPresidentsAsync(president);_whiteHouseAppService.InsertPresidentAsync(new PresidentDto("1", "New President", "12345678")); // Assert response.Data = president; } return response; } } |
Caso as regras de negocio sejam satisfeitas, o repositório é consumido.
Assert.True(response.Success);
}
} |
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 das camadas carregadas em nosso teste através de nosso modulo que foi definido.
Com o NSubstitute criamos o objeto e então fazemos sua substituição dentro da estrutura de IoC do framework, possibilitando assim consumir nosso mockEm nossos casos de teste, fazemos o mock no ultimo nível de infra estrutura, usando o NSubstitute para realizar o mock da interface IWhiteHouseRepository.
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.
Todos os testes integrados visto até o presente momento podem ser aplicados no teste da camada de serviço.
...