Newer
Older
Warehouse / tests / Application.IntegrationTests / TestFramework.cs
@Derek Comartin Derek Comartin on 22 Aug 2023 4 KB Init
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);
        }
    }
}