using MediatR; using Microsoft.Extensions.DependencyInjection; using MyWarehouse.Domain.Common; using MyWarehouse.Domain.Partners; using MyWarehouse.Domain.Products; using MyWarehouse.Domain.Transactions; using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Threading.Tasks; namespace MyWarehouse.Application.IntegrationTests { [SetUpFixture] public class TestFramework { private static TestHost _host; private static TestData _data; private static TestDataFactory _dataFactory; [OneTimeSetUp] public void SetUpEnvironment() { _host = TestHost.Create(); _data = new TestData(_host); _dataFactory = new TestDataFactory(_data); } /// <summary> /// Executes the provided request in a newly instantiated scope, /// to isolate it from the rest of the test. Be aware that test DbContext /// will be renewed during this call, to ensure that test code won't pick up /// cached entities, because that could falsify test results. /// <see cref="https://stackoverflow.com/a/17051820/2906385"/> /// </summary> // Note that serving everyhing with AsNoTracking() to the tests is // not a good alternative to context renewal, because it would lead to DB PK collisions // when tests try to insert entities with untracked navigation properties set on them. public static async Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request) { _data.DisposeScope(); using var scope = _host.ScopeFactory.CreateScope(); var mediator = scope.ServiceProvider.GetService<IMediator>(); var result = await mediator.Send(request); _data.CreateScope(); return result; } // TODO: Find a better solution. Especially because this hides the method documentation. // Methods are exposed this way because I like structured access, but if I used properties, FluentAssertions // would have polluted the intellisense list with its extensions methods, which I find extremely annoying. public static class Data { public static Task<int> AddAsync<TEntity>(TEntity entity) where TEntity : IEntity => _data.AddAsync(entity); public static Task AddRangeAsync<TEntity>(params TEntity[] entities) where TEntity : class, IEntity => _data.AddRangeAsync(entities); public static Task<List<TEntity>> GetAllAsync<TEntity>(params Expression<Func<TEntity, object>>[] includes) where TEntity : class, IEntity => _data.GetAllAsync(includes); public static Task<TEntity> FindAsync<TEntity>(int id, params Expression<Func<TEntity, object>>[] includes) where TEntity : class, IEntity => _data.FindAsync(id, includes); public static Task ResetDatabase() => _data.ResetDatabase(); } public static class Context { public static void CreateScope() => _data.CreateScope(); public static void DisposeScope() => _data.DisposeScope(); } public static class DataFactory { public static Task<List<Partner>> AddPartners(int howMany, Action<(int index, Partner partner)> partnerOverrides = null) => _dataFactory.AddPartners(howMany, partnerOverrides); public static Task<List<Product>> AddProducts(int howMany, Action<(int index, Product product)> productOverrides = null) => _dataFactory.AddProducts(howMany, productOverrides); public static Task<Transaction> CreateProcurementTransaction(int howManyLines, Partner partner = null, bool createNewProducts = true) => _dataFactory.CreateProcurementTransaction(howManyLines, partner, createNewProducts); public static Task<Transaction> CreateSalesTransaction(int howManyLines, Partner partner = null) => _dataFactory.CreateSalesTransaction(howManyLines, partner); public static Task<Transaction> CreateSalesTransaction(Partner partner, IEnumerable<Product> productsToSell, float ratioOfStockToSell = 1) => _dataFactory.CreateSalesTransaction(partner, productsToSell, ratioOfStockToSell); } } }