using System; using System.Collections.Generic; using System.Diagnostics; using System.IO.Abstractions; using System.IO.Abstractions.TestingHelpers; using System.Linq; using System.Reflection; using DryIoc; using Moq; using NzbDrone.Common.Composition; using NzbDrone.Common.Disk; using NzbDrone.Common.EnvironmentInfo; namespace NzbDrone.Test.Common.AutoMoq { [DebuggerStepThrough] public class AutoMoqer { public readonly MockBehavior DefaultBehavior = MockBehavior.Default; private readonly IContainer _container; private readonly IDictionary _registeredMocks = new Dictionary(); public AutoMoqer() { _container = CreateTestContainer(new Container(rules => rules.WithMicrosoftDependencyInjectionRules().WithDefaultReuse(Reuse.Singleton))); LoadPlatformLibrary(); AssemblyLoader.RegisterSQLiteResolver(); } public IContainer Container => _container; public virtual T Resolve() { var result = _container.Resolve(); SetConstant(result); return result; } public virtual T Resolve(object serviceKey) { var result = _container.Resolve(serviceKey: serviceKey); SetConstant(result); return result; } public virtual Mock GetMock() where T : class { return GetMock(DefaultBehavior); } public virtual Mock GetMock(MockBehavior behavior) where T : class { var type = typeof(T); if (GetMockHasNotBeenCalledForThisType(type)) { CreateANewMockAndRegisterIt(type, behavior); } var mock = TheRegisteredMockForThisType(type); if (behavior != MockBehavior.Default && mock.Behavior == MockBehavior.Default) { throw new InvalidOperationException("Unable to change be behaviour of a an existing mock."); } return mock; } public virtual void SetMock(Type type, Mock mock) { if (GetMockHasNotBeenCalledForThisType(type)) { _registeredMocks.Add(type, mock); } if (mock != null) { _container.RegisterInstance(type, mock.Object, ifAlreadyRegistered: IfAlreadyRegistered.Replace); } } public virtual void SetConstant(T instance) { _container.RegisterInstance(instance, ifAlreadyRegistered: IfAlreadyRegistered.Replace); SetMock(instance.GetType(), null); } private IContainer CreateTestContainer(IContainer container) { var c = container.CreateChild(IfAlreadyRegistered.Replace, container.Rules .WithDynamicRegistration((serviceType, serviceKey) => { // ignore services with non-default key if (serviceKey != null) { return null; } if (serviceType == typeof(object)) { return null; } if (serviceType.IsGenericType && serviceType.IsOpenGeneric()) { return null; } if (serviceType == typeof(System.Text.Json.Serialization.JsonConverter)) { return null; } // get the Mock object for the abstract class or interface if (serviceType.IsInterface || serviceType.IsAbstract) { return new[] { new DynamicRegistration(GetMockFactory(serviceType), IfAlreadyRegistered.Keep) }; } // concrete types var concreteTypeFactory = serviceType.ToFactory(Reuse.Singleton, FactoryMethod.ConstructorWithResolvableArgumentsIncludingNonPublic); return new[] { new DynamicRegistration(concreteTypeFactory) }; }, DynamicRegistrationFlags.Service | DynamicRegistrationFlags.AsFallback)); c.Register(typeof(Mock<>), Reuse.Singleton, FactoryMethod.DefaultConstructor()); return c; } private Mock TheRegisteredMockForThisType(Type type) where T : class { return (Mock)_registeredMocks.First(x => x.Key == type).Value; } private void CreateANewMockAndRegisterIt(Type type, MockBehavior behavior) where T : class { var mock = new Mock(behavior); _container.RegisterInstance(mock.Object); SetMock(type, mock); } private bool GetMockHasNotBeenCalledForThisType(Type type) { return !_registeredMocks.ContainsKey(type); } private DelegateFactory GetMockFactory(Type serviceType) { var mockType = typeof(Mock<>).MakeGenericType(serviceType); return DelegateFactory.Of(r => { var mock = (Mock)r.Resolve(mockType); SetMock(serviceType, mock); return mock.Object; }, Reuse.Singleton); } private void LoadPlatformLibrary() { var assemblyName = "Lidarr.Windows"; if (OsInfo.IsNotWindows) { assemblyName = "Lidarr.Mono"; } var types = Assembly.Load(assemblyName).GetTypes(); var diskProvider = types.SingleOrDefault(x => x.Name == "DiskProvider"); // The standard dynamic mock registrations, explicit so DryIoC doesn't get confused when we add alternatives _container.Register(typeof(IFileSystem), GetMockFactory(typeof(IFileSystem))); _container.Register(typeof(IDiskProvider), GetMockFactory(typeof(IDiskProvider))); // A concrete registration from the platform library using a mock filesystem _container.RegisterInstance(new MockFileSystem(), serviceKey: FileSystemType.Mock); _container.Register(typeof(IDiskProvider), diskProvider, made: Parameters.Of.Type(serviceKey: FileSystemType.Mock), serviceKey: FileSystemType.Mock); // A concrete registration from the platform library using the actual filesystem _container.Register(serviceKey: FileSystemType.Actual); _container.Register(typeof(IDiskProvider), diskProvider, made: Parameters.Of.Type(serviceKey: FileSystemType.Actual), serviceKey: FileSystemType.Actual); } } }