namespace MyProject.Tests;
public abstract class WithSubject<TSubject> where TSubject : class
private readonly Dictionary<Type, object?> _explicitDependencies = new();
private readonly Dictionary<Type, Mock> _dependencies = new();
private TSubject? _subject;
protected Mock<TDependency> The<TDependency>() where TDependency : class
if (!_dependencies.ContainsKey( typeof(TDependency) ))
_dependencies.Add( typeof(TDependency), new Mock<TDependency>() );
return (Mock<TDependency>) _dependencies[typeof(TDependency)];
protected Mock<TDependency> An<TDependency>() where TDependency : class
return new Mock<TDependency>();
protected void UseFor<TDependency>(TDependency? implementation)
_explicitDependencies.TryAdd(typeof(TDependency), implementation);
protected TSubject Subject
if (_subject is not null)
_subject = BuildSubject();
catch (InvalidOperationException e)
throw new Exception($"{typeof(TSubject).Name} does not have a constructor that works with BuildSubject, and must be built manually", e);
private TSubject BuildSubject()
var subjectType = typeof(TSubject);
var bestConstructor = subjectType
.MaxBy(c => c.GetParameters().Length);
if (bestConstructor is null)
throw new InvalidOperationException("No suitable constructors");
var constructorParameters = new List<object?>();
foreach (var p in bestConstructor.GetParameters())
var parameterType = p.ParameterType;
if (!_explicitDependencies.TryGetValue(parameterType, out var implementation))
if (!_dependencies.TryGetValue(parameterType, out var mock))
mock = CreateMock(parameterType);
_dependencies.TryAdd(parameterType, mock);
implementation = mock.Object;
constructorParameters.Add(implementation);
var instance = Activator.CreateInstance(subjectType, constructorParameters.ToArray());
throw new InvalidOperationException($"Could not construct an instance of {subjectType.Name}");
return (TSubject)instance;
private Mock CreateMock(Type dependencyType)
var openMock = typeof(Mock<>).MakeGenericType(dependencyType);
return (Mock)Activator.CreateInstance(openMock)!;