Хотя я согласен с советом Стивена и Криса, разработчики не всегда отвечают за инфраструктуру, которую они должны использовать. Где возможно, я буду настаивать на Castle.Windsor, так как это то, с чем моя команда больше всего знакома, но в этом случае нам удалось собрать воедино те тесты, которые мы хотели сами.
Я представляю здесь тестовый образецв случае, если кто-то сталкивается с этим вопросом и испытывает те же трудности, но, пожалуйста, рассмотрите возможность использования лучшего поставщика DI, особенно если вы все еще находитесь в начале вашего проекта.
[Test]
public void Assert_Lifetimes_Are_Consistent()
{
var missing = new List<string>();
var errors = new HashSet<Tuple<string, string>>();
foreach (var service in _serviceCollection.Where(s => IsInYourAssembly(s.ServiceType)))
{
var serviceLifetimeRanking = LifetimeRanking(service.Lifetime);
foreach (var fieldInfo in ((System.Reflection.TypeInfo)service.ServiceType).DeclaredFields.Where(fi => fi.FieldType.IsAbstract && IsInYourAssembly(fi.FieldType)))
{
var dependencyLifetime = _serviceCollection.SingleOrDefault(fi => fi.ServiceType == fieldInfo.FieldType)?.Lifetime;
if (dependencyLifetime==null)
missing.Add($"No service found for {fieldInfo.FieldType.FullName} as a dependency for {service.ServiceType.FullName}");
var dependencyLifetimeRanking = LifetimeRanking(dependencyLifetime);
if (dependencyLifetimeRanking > serviceLifetimeRanking)
errors.Add(
Tuple.Create(
$"{service.ServiceType.Name} ({service.Lifetime})",
$"{fieldInfo.FieldType.Name} ({dependencyLifetime})"
)
);
}
}
if (missing.Any()||errors.Any())
{
var sb = new StringBuilder();
sb.AppendJoin(Environment.NewLine, missing);
if (errors.Any())
{
sb.AppendLine("Following dependency pairs have inconsistent lifestyles:");
sb.AppendLine(string.Join(Environment.NewLine, errors.Select(err => $"{err.Item1} -> {err.Item2}")));
}
Assert.Fail(sb.ToString());
}
}
private bool IsInYourAssembly(Type type)
{
return (type.Assembly.FullName?.IndexOf("YOUR_PROJECT_ASSEMBLY_HERE") ?? -1) == 0;
}
private int LifetimeRanking(ServiceLifetime serviceLifetime)
{
switch (serviceLifetime)
{
case ServiceLifetime.Singleton:
return 1;
case ServiceLifetime.Scoped:
return 2;
case ServiceLifetime.Transient:
return 3;
default:
throw new ArgumentOutOfRangeException("serviceLifetime", serviceLifetime,
$"Value is not a known member of the ServiceLifetime enum");
}
}
Если тест не пройден,он вернет список отсутствующих зависимостей, за которым следует список несовместимых сроков зависимости.
Поле _serviceCollection
должно быть заполнено и Startup(config, env).ConfigureServices(_serviceCollection);
необходимо вызвать перед выполнением теста.
IsInYourAssembly - важная функция для фильтрации всех универсальных типов, которые также возвращаются в _serviceCollection
.