Árvore de páginas

Versões comparadas

Chave

  • Esta linha foi adicionada.
  • Esta linha foi removida.
  • A formatação mudou.
Índice

Introdução

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.

...

  • Microsoft.NET.Test.Sdk
  • NSubstitute
  • Shouldly
  • Tnf.TestBase.AppTnf.App.EntityFrameworkCore.TestBase
  • Microsoft.EntityFrameworkCore.InMemory
  • xunit
  • xunit.runner.visualstudio

...

  • Microsoft.NET.Test.Sdk
  • Microsoft.AspNetCore.TestHost
  • NSubstitute
  • Shouldly
  • Tnf.App.AspNetCore.TestBase
  • Tnf.App.EntityFrameworkCore.TestBase
  • xunit
  • xunit.runner.visualstudio

...

Testes com Entity Framework Core em memória.

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
languagec#
firstline1
titleAppTestModule
linenumberstrue
[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
languagec#
firstline1
titleCountryAppServiceTests.cs
linenumberstrue
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
languagec#
firstline1
titleAppTestModule.cs
linenumberstrue
[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
languagec#
firstline1
titleAppTestBase.cs
linenumberstrue
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
languagec#
firstline1
titleWhiteHouseAppService
linenumberstrue
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.

Testes na camada de serviços

...

Bloco de código
languagec#
firstline1
titleAppTestModule.cs
linenumberstrue
[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
languagec#
firstline1
titleStartupTest.cs
linenumberstrue
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?}");
        });
	}
}

...

  • AddEntityFrameworkInMemoryDatabase: Neste ponto configuramos o uso do Entity Framework em memoria para o pipeline do AspNetCore;
  • AddTnfAddTnfApp: 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);

...

Bloco de código
languagec#
firstline1
titleWhiteHouseControllerTests.cs
linenumberstrue
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);
		                $"{RouteConsts.WhiteHouse}?pageSize=5",
                           HttpStatusCode.OK
                       );

        // Assert
        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".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.

...