using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using AutoFixture; using AutoFixture.Kernel; namespace Bit.Test.Common.AutoFixture { public class SutProvider<TSut> : ISutProvider { private Dictionary<Type, Dictionary<string, object>> _dependencies; private readonly IFixture _fixture; private readonly ConstructorParameterRelay<TSut> _constructorParameterRelay; public TSut Sut { get; private set; } public Type SutType => typeof(TSut); public SutProvider() { _dependencies = new Dictionary<Type, Dictionary<string, object>>(); _fixture = new Fixture().WithAutoNSubstitutions(); _constructorParameterRelay = new ConstructorParameterRelay<TSut>(this, _fixture); _fixture.Customizations.Add(_constructorParameterRelay); } public SutProvider<TSut> SetDependency<T>(T dependency, string parameterName = "") => SetDependency(typeof(T), dependency, parameterName); public SutProvider<TSut> SetDependency(Type dependencyType, object dependency, string parameterName = "") { if (_dependencies.ContainsKey(dependencyType)) { _dependencies[dependencyType][parameterName] = dependency; } else { _dependencies[dependencyType] = new Dictionary<string, object> { { parameterName, dependency } }; } return this; } public T GetDependency<T>(string parameterName = "") => (T)GetDependency(typeof(T), parameterName); public object GetDependency(Type dependencyType, string parameterName = "") { if (DependencyIsSet(dependencyType, parameterName)) { return _dependencies[dependencyType].ContainsKey(parameterName) ? _dependencies[dependencyType][parameterName] : _dependencies[dependencyType][""]; } 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<Type, Dictionary<string, object>>(); Sut = default; } ISutProvider ISutProvider.Create() => Create(); public SutProvider<TSut> Create() { Sut = _fixture.Create<TSut>(); return this; } private bool DependencyIsSet(Type dependencyType, string parameterName = "") => _dependencies.ContainsKey(dependencyType) && (_dependencies[dependencyType].ContainsKey(parameterName) || _dependencies[dependencyType].ContainsKey("")); private object GetDefault(Type type) => type.IsValueType ? Activator.CreateInstance(type) : null; private class ConstructorParameterRelay<T> : ISpecimenBuilder { private readonly SutProvider<T> _sutProvider; private readonly IFixture _fixture; public ConstructorParameterRelay(SutProvider<T> 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); } // This is the equivalent of _fixture.Create<parameterInfo.ParameterType>, 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; } } } }