Skip to content

TestCore

Mehmet Özkaya edited this page Apr 21, 2019 · 4 revisions

The test project consists of 4 different projects as in the main structure. These projects form separate test projects for each of the projects (Core-Infrastructure-Application-Web) in the existing architecture. All these projects are XUnit Project template of Visual Studio 2019.

As the same way, the development of the test projects should be Core -> Infrastructure -> Application -> Web layer in respectively.

So lets start to build test projects.

AspnetRun.Core.Tests

This project for testing project of AspnetRun.Core class library project. So it should be test includings of this AspnetRun.Core class library classes.

The main folder structure of AspnetRun.Core.Tests project are should have below folders;

  • Builder
  • Entities
  • Specifications

So in example of Entities test ProductTests.cs class includes some of use case of Product entity.

[Fact]
public void Create_Product()
{
	var product = Product.Create(_testProductId, _testCategoryId, _testProductName, _testUnitPrice, _testQuantity, null, null, false);

	Assert.Equal(_testProductId, product.Id);
	Assert.Equal(_testCategoryId, product.CategoryId);
	Assert.Equal(_testProductName, product.ProductName);
	Assert.Equal(_testUnitPrice, product.UnitPrice);
	Assert.Equal(_testQuantity, product.UnitsInStock);
}

As you can see that this test classes only test for Core Project in order to ensure Product entity class Create method is working properly.

As the same way another test could be for Specification classes. As per the Core layer has not any dependency of database providers, we should apply Specification tests with Mock Product list provided from ProductBuilder.cs. Check the ProductSpecificationTests.cs.

[Fact]
public void Matches_Product_With_Category_Spec()
{
	var spec = new ProductWithCategorySpecification(ProductBuilder.ProductName1);

	var result = ProductBuilder.GetProductCollection()
		.AsQueryable()
		.FirstOrDefault(spec.Criteria);

	Assert.NotNull(result);
	Assert.Equal(ProductBuilder.ProductId1, result.Id);
}

AspnetRun.Infrastructure.Tests

This project for testing project of AspnetRun.Infrastructure class library project. So it should be test includings of this AspnetRun.Infrastructure class library classes.

The main folder structure of AspnetRun.Infrastructure.Tests project are should have below folders;

  • Builder
  • Repositories

So in example of Entities test ProductTests.cs class includes some of use case of Product repository.

[Fact]
public async Task Get_Existing_Product()
{
	var existingProduct = ProductBuilder.WithDefaultValues();
	_aspnetRunContext.Products.Add(existingProduct);           
	_aspnetRunContext.SaveChanges();

	var productId = existingProduct.Id;
	_output.WriteLine($"ProductId: {productId}");

	var productFromRepo = await _productRepository.GetByIdAsync(productId);
	Assert.Equal(ProductBuilder.TestProductId, productFromRepo.Id);
	Assert.Equal(ProductBuilder.TestCategoryId, productFromRepo.CategoryId);
}

As you can see that, in this layer of tests directly use repository without mocking this objects. That means we should create the EF.Core database providers. So before tests run in constructor of this class we bootstrapped EF.Core database provider as below code;

[Fact]
private readonly AspnetRunContext _aspnetRunContext;
private readonly ProductRepository _productRepository;
private readonly ITestOutputHelper _output;

public ProductTests(ITestOutputHelper output)
{
	_output = output;
	var dbOptions = new DbContextOptionsBuilder<AspnetRunContext>()
		.UseInMemoryDatabase(databaseName: "AspnetRun")
		.Options;
	_aspnetRunContext = new AspnetRunContext(dbOptions);
	_productRepository = new ProductRepository(_aspnetRunContext);
}

We used InMemoryDatabase mode in EF.Core for creating context object for testing purpose. You can check other test methods in ProductTests.cs class in AspnetRun.Infrastructure.Tests assembly like Get_Product_By_Name, Get_Product_By_Category.

AspnetRun.Application.Tests

This project for testing project of AspnetRun.Application class library project. So it should be test includings of this AspnetRun.Application class library classes.

The main folder structure of AspnetRun.Application.Tests project are should have below folders;

  • Builder
  • Services

So in example of Entities test ProductTests.cs class includes some of use case of Product application.

[Fact]
public async Task Create_New_Product()
{
	var category = Category.Create(It.IsAny<int>(), It.IsAny<string>());
	var product = Product.Create(It.IsAny<int>(), category.Id, It.IsAny<string>());
	Product nullProduct = null; // we gave null product in order to create new one, if you give real product it returns already existing error

	_mockProductRepository.Setup(x => x.GetByIdAsync(It.IsAny<int>())).ReturnsAsync(nullProduct);
	_mockProductRepository.Setup(x => x.AddAsync(product)).ReturnsAsync(product);            

	var productService = new ProductAppService(_mockProductRepository.Object, _mockAppLogger.Object);
	var createdProductDto = await productService.Create(new Dtos.ProductDto { Id = product.Id, CategoryId = product.CategoryId, ProductName = product.ProductName });

	_mockProductRepository.Verify(x => x.GetByIdAsync(It.IsAny<int>()), Times.Once);
	_mockProductRepository.Verify(x => x.AddAsync(product), Times.Once);
}

As you can see that, in this layer of tests are not directly use repository objects. Instead of this, using Moq library and create mocked objects. That means we should create the Mocked objects and SetUp this objects. So before tests run in constructor of this class we bootstrapped Moq provider as below code;

private Mock<IProductRepository> _mockProductRepository;
private Mock<IAsyncRepository<Category>> _mockCategoryRepository;
private Mock<IAppLogger<ProductAppService>> _mockAppLogger;

public ProductTests()
{
	_mockProductRepository = new Mock<IProductRepository>();
	_mockCategoryRepository = new Mock<IAsyncRepository<Category>>();
	_mockAppLogger = new Mock<IAppLogger<ProductAppService>>();
}

AspnetRun.Web.Tests

This project for testing project of AspnetRun.Web class library project. So it should be test includings of this AspnetRun.Web class library classes.

The main folder structure of AspnetRun.Web.Tests project are should have below folders;

  • Pages

So in example of Entities test ProductPageTests.cs class includes some of use case of Product application.

[Fact]
public async Task Product_Page_Test()
{
	// Arrange & Act
	var response = await Client.GetAsync("/Product");
	response.EnsureSuccessStatusCode();
	var stringResponse = await response.Content.ReadAsStringAsync();

	// Assert
	Assert.Contains("Samsung", stringResponse);
}

As you can see that, in this layer of tests are directly request to HttpClient objects. That means we should bootstrap the Web Application from scratch. So before run tests we have another class CustomWebApplicationFactory.cs which provide to up Web Application for testing purpose.

[Fact]
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
	builder.ConfigureServices(services =>
	{
		services.AddEntityFrameworkInMemoryDatabase();

		// Create a new service provider.
		var provider = services
			.AddEntityFrameworkInMemoryDatabase()
			.BuildServiceProvider();

		// Add a database context (ApplicationDbContext) using an in-memory 
		// database for testing.
		services.AddDbContext<AspnetRunContext>(options =>
		{
			options.UseInMemoryDatabase("AspnetRun");
			options.UseInternalServiceProvider(provider);
		});

		// Build the service provider.
		var sp = services.BuildServiceProvider();

		// Create a scope to obtain a reference to the database
		// context (ApplicationDbContext).
		using (var scope = sp.CreateScope())
		{
			var scopedServices = scope.ServiceProvider;
			var db = scopedServices.GetRequiredService<AspnetRunContext>();
			var loggerFactory = scopedServices.GetRequiredService<ILoggerFactory>();

			var logger = scopedServices
				.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();

			// Ensure the database is created.
			db.Database.EnsureCreated();

			try
			{
				// Seed the database with test data.
				AspnetRunContextSeed.SeedAsync(db, loggerFactory).Wait();
			}
			catch (Exception ex)
			{
				logger.LogError(ex, $"An error occurred seeding the " +
					"database with test messages. Error: {ex.Message}");
			}
		}
	});
}

As you can see that asp.net web application could be run on test methods by using convention power of asp.net core.

Summary

Asp.net core is very powerful framework when it comes to test it functional way. So you can test every options of your web application.

AspNetRun is give a base test infrastructure of your next enterprise web application with layered application. Using Domain Driven Design Layers and every layer has own test project and built-in test methods which you can continue to provide your custom use cases.

Clone this wiki locally