using System.Reflection; using AutoFixture; using AutoFixture.Kernel; namespace Bit.Test.Common.AutoFixture; public class SutProvider : ISutProvider { private Dictionary> _dependencies; private readonly IFixture _fixture; private readonly ConstructorParameterRelay _constructorParameterRelay; public TSut Sut { get; private set; } public Type SutType => typeof(TSut); public SutProvider() : this(new Fixture()) { } public SutProvider(IFixture fixture) { _dependencies = new Dictionary>(); _fixture = (fixture ?? new Fixture()).WithAutoNSubstitutions().Customize(new GlobalSettings()); _constructorParameterRelay = new ConstructorParameterRelay(this, _fixture); _fixture.Customizations.Add(_constructorParameterRelay); } public SutProvider SetDependency(T dependency, string parameterName = "") => SetDependency(typeof(T), dependency, parameterName); public SutProvider SetDependency(Type dependencyType, object dependency, string parameterName = "") { if (_dependencies.ContainsKey(dependencyType)) { _dependencies[dependencyType][parameterName] = dependency; } else { _dependencies[dependencyType] = new Dictionary { { parameterName, dependency } }; } return this; } public T GetDependency(string parameterName = "") => (T)GetDependency(typeof(T), parameterName); public object GetDependency(Type dependencyType, string parameterName = "") { if (DependencyIsSet(dependencyType, parameterName)) { return _dependencies[dependencyType][parameterName]; } else if (_dependencies.ContainsKey(dependencyType)) { var knownDependencies = _dependencies[dependencyType]; if (knownDependencies.Values.Count == 1) { return _dependencies[dependencyType].Values.Single(); } else { throw new ArgumentException(string.Concat($"Dependency of type {dependencyType.Name} and name ", $"{parameterName} does not exist. Available dependency names are: ", string.Join(", ", knownDependencies.Keys))); } } else { throw new ArgumentException($"Dependency of type {dependencyType.Name} and name {parameterName} has not been set."); } } public void Reset() { _dependencies = new Dictionary>(); Sut = default; } ISutProvider ISutProvider.Create() => Create(); public SutProvider Create() { Sut = _fixture.Create(); return this; } private bool DependencyIsSet(Type dependencyType, string parameterName = "") => _dependencies.ContainsKey(dependencyType) && _dependencies[dependencyType].ContainsKey(parameterName); private object GetDefault(Type type) => type.IsValueType ? Activator.CreateInstance(type) : null; private class ConstructorParameterRelay : ISpecimenBuilder { private readonly SutProvider _sutProvider; private readonly IFixture _fixture; public ConstructorParameterRelay(SutProvider sutProvider, IFixture fixture) { _sutProvider = sutProvider; _fixture = fixture; } public object Create(object request, ISpecimenContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (!(request is ParameterInfo parameterInfo)) { return new NoSpecimen(); } if (parameterInfo.Member.DeclaringType != typeof(T) || parameterInfo.Member.MemberType != MemberTypes.Constructor) { return new NoSpecimen(); } if (_sutProvider.DependencyIsSet(parameterInfo.ParameterType, parameterInfo.Name)) { return _sutProvider.GetDependency(parameterInfo.ParameterType, parameterInfo.Name); } // Return default type if set else if (_sutProvider.DependencyIsSet(parameterInfo.ParameterType, "")) { return _sutProvider.GetDependency(parameterInfo.ParameterType, ""); } // This is the equivalent of _fixture.Create, but no overload for // Create(Type type) exists. var dependency = new SpecimenContext(_fixture).Resolve(new SeededRequest(parameterInfo.ParameterType, _sutProvider.GetDefault(parameterInfo.ParameterType))); _sutProvider.SetDependency(parameterInfo.ParameterType, dependency, parameterInfo.Name); return dependency; } } }