首页 > 解决方案 > 确保对象的所有实例都具有具有单元测试的抽象类的实现

问题描述

我正在尝试编写一个单元测试,以确保 Ardalis SmartEnum 的所有变体都具有相应的泛型类实现。

以枚举为例。

public sealed class DomainEntityEnum : SmartEnum<DomainEntityEnum, string>
{
    public static readonly DomainEntityEnum Foo = new DomainEntityEnum(nameof(Foo), "Foo");
    public static readonly DomainEntityEnum Bar = new DomainEntityEnum(nameof(Bar), "Bar");
    public static readonly DomainEntityEnum Baz = new DomainEntityEnum(nameof(Baz), "Baz");

    private DomainEntityEnum(string name, string value)
        : base(name, value)
    {
    }
}

现在学习这个通用类

public interface IBaseDomainLocator
{
    Task<int?> GetSomeItemId(IDbContext dbContext, DomainEntityEnum domainEntityEnum, int domainEntityResourceId);
}
abstract class BaseDomainLocator<T> : IBaseDomainLocator where T : DomainEntity
{
    protected abstract DomainEntityEnum DomainEntity { get; }

    protected virtual Expression<Func<T, bool>> IdFilter(int id) => q => q.GetAsResourceItemId().Id == id;
    protected virtual Expression<Func<T, int?>> SomeItemSelector() => null;

    public async Task<int?> GetSomeItemId(IDbContext dbContext, DomainEntityEnum domainEntityEnum, int domainEntityResourceId)
    {
        if (domainEntityEnum != DomainEntity)
            return null;

        if (SomeItemSelector() != null)
        {
            return await dbContext.Set<T>()
                .Where(IdFilter(domainEntityResourceId))
                .Select(SomeItemSelector())
                .SingleOrDefaultAsync();
        }

        return null;
    }
}

我们创建了一个目录,然后我们可以根据 DomainEntityEnum 的值获取特定的实例。

public static class DomainLocatorServiceMap
{
    private static Dictionary<DomainEntityEnum, Type> sourceTypeToProviderKvps { get; }

    static DomainLocatorServiceMap()
    {
        sourceTypeToProviderKvps = new Dictionary<DomainEntityEnum, Type>();
    }

    public static Type GetDomainLocatorType(DomainEntityEnum domainEntity)
    {
        sourceTypeToProviderKvps.TryGetValue(domainEntity, out Type domainLocator);
        return domainLocator;
    }

    public static Type GetRequiredDomainLocatorType(DomainEntityEnum domainEntity) 
        => sourceTypeToProviderKvps[domainEntity];

    public static void AssociateWithScopedServiceBind<TService, TImplementation>(IServiceCollection services, DomainEntityEnum domainEntity)
        where TService : class
        where TImplementation : class, TService
    {
        services.AddScoped<TService, TImplementation>();
        AssociateDomainLocator(domainEntity, typeof(TService));
    }

    private static void AssociateDomainLocator(DomainEntityEnum domainEntity, Type serviceType)
    {
        if (!typeof(IBaseDomainLocator).IsAssignableFrom(serviceType))
            throw new Exception("IBaseDomainLocator invalid");

        sourceTypeToProviderKvps[domainEntity] = serviceType;
    }
}

public interface IDomainLocatorServiceCatalog
{
    IBaseDomainLocator GetDomainLocatorService(DomainEntityEnum domainEntity);
}
class DomainLocatorServiceCatalog : IDomainLocatorServiceCatalog
{
    private readonly IServiceProvider _serviceProvider = null;

    public DomainLocatorServiceCatalog(IServiceProvider serviceProvider)
    {
          serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
    }

    public IBaseDomainLocator GetDomainLocatorService(DomainEntityEnum domainEntity)
    {
        Type serviceType = DomainLocatorServiceMap.GetDomainLocatorType(domainEntity);
        if (serviceType != null)
        {
            object service = _serviceProvider.GetRequiredService(serviceType);
            IBaseDomainLocator domainLocator = (IBaseDomainLocator)service;
            return domainLocator;
        }
        throw new Exception("Invalid Domain Entity");
    }
}

实现看起来像这样

internal interface IBazDomainLocator : IBaseDomainLocator { }
class BazDomainLocator : BaseDomainLocator<Baz>, IBazDomainLocator
{
    protected override DomainEntityEnum DomainEntity => DomainEntityEnum.Baz;
    protected override Expression<Func<Baz, int?>> SomeItemSelector() => q => q.SomeItemId;
}

而这些都是通过依赖注入注册的

internal static class DomainFinderDependencyInjection
{
    public static IServiceCollection AddDomainFinderServices(this IServiceCollection services)
    {
        DomainLocatorServiceMap.AssociateWithScopedServiceBind<IBazDomainLocator, BazDomainLocator>(services, DomainEntityEnum.Baz);

        return services;
    }
}

考虑到我需要 IServiceProvider 来查找 SmartEnum 值的域定位器的实现,我如何编写测试以确保枚举的所有值都具有 IBaseDomainLocator 的实现?使用 DomainEntityEnum.List 循环遍历枚举中包含的所有值非常容易,但我需要某种方法从服务提供商处解析 GetRequiredService(最好不必复制 DomainFinderDependencyInjection)

标签: c#unit-testinggenerics.net-coredependency-injection

解决方案


推荐阅读