using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FluentAssertions;
using My.Data;
using My.Web.Controllers;
using Xunit;
namespace My.Web.Tests {
public class OiYouThereGetOutTests {
[Fact]
public void My_Controllers_do_not_depend_upon_the_database() {
var myConcreteTypes = GetMyAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.ToArray();
var controllerTypes = typeof(My.Web.Startup).Assembly.GetTypes()
.Where(myWebType =>
myWebType != typeof(Microsoft.AspNetCore.Mvc.Controller) &&
typeof(Microsoft.AspNetCore.Mvc.Controller).IsAssignableFrom(myWebType));
foreach (var controllerType in controllerTypes) {
var allTheTypes = GetDependentTypes(controllerType, myConcreteTypes);
allTheTypes.Count.Should().BeGreaterThan(0);
var dependsUponTheDatabase = allTheTypes.Where(keyValue => keyValue.Key == typeof(MyDbContext));
dependsUponTheDatabase.Any().Should().Be(false, because: $"{controllerType} depends upon the database through {string.Join(", ", dependsUponTheDatabase.Select(dod => dod.Value))}");
}
}
private static Dictionary<Type, Type> GetDependentTypes(Type type, Type[] typesToCheck, Dictionary<Type, Type> typesSoFar = null) {
var types = typesSoFar ?? new Dictionary<Type, Type>();
foreach (var constructor in type.GetConstructors().Where(ctor => ctor.IsPublic)) {
foreach (var parameter in constructor.GetParameters()) {
if (parameter.ParameterType.IsInterface) {
if (parameter.ParameterType.IsGenericType) {
foreach (var genericType in parameter.ParameterType.GenericTypeArguments) {
AddIfMissing(types, genericType, type);
}
} else {
var typesImplementingInterface = TypesImplementingInterface(parameter.ParameterType, typesToCheck);
foreach (var typeImplementingInterface in typesImplementingInterface) {
AddIfMissing(types, typeImplementingInterface, type);
AddIfMissing(types, GetDependentTypes(typeImplementingInterface, typesToCheck, types).Keys.ToList(), type);
}
}
} else {
AddIfMissing(types, parameter.ParameterType, type);
AddIfMissing(types, GetDependentTypes(parameter.ParameterType, typesToCheck, types).Keys.ToList(), type);
}
}
}
return types;
}
private static void AddIfMissing(Dictionary<Type, Type> types, Type typeToAdd, Type parentType) {
if (!types.Keys.Contains(typeToAdd))
types.Add(typeToAdd, parentType);
}
private static void AddIfMissing(Dictionary<Type, Type> types, IList<Type> typesToAdd, Type parentType) {
foreach (var typeToAdd in typesToAdd) {
AddIfMissing(types, typeToAdd, parentType);
}
}
private static Type[] TypesImplementingInterface(Type interfaceType, Type[] typesToCheck) =>
typesToCheck.Where(type => !type.IsInterface && interfaceType.IsAssignableFrom(type)).ToArray();
private static bool IsRealClass(Type testType) =>
testType.IsAbstract == false &&
testType.IsGenericType == false &&
testType.IsGenericTypeDefinition == false &&
testType.IsInterface == false;
private static Assembly[] GetMyAssemblies() =>
AppDomain
.CurrentDomain
.GetAssemblies()
// Not strictly necessary but it reduces the amount of types returned
.Where(assembly => assembly.GetName().Name.StartsWith("My"))
.ToArray();
}
}