From c2073ecb50b06f99cd1817be600c2ae89face438 Mon Sep 17 00:00:00 2001 From: alistairjevans Date: Sun, 22 Mar 2020 10:52:26 +0000 Subject: [PATCH] Explicitly state that mock has been requested, so objects that cannot be mocked throw the right exception. --- src/Autofac.Extras.Moq/AutoMock.cs | 13 ++++++-- .../MoqRegistrationHandler.cs | 31 +++++++++++++---- .../AutoMockFixture.cs | 33 +++++++++++++++++++ .../MoqRegistrationHandlerFixture.cs | 6 ++-- 4 files changed, 70 insertions(+), 13 deletions(-) diff --git a/src/Autofac.Extras.Moq/AutoMock.cs b/src/Autofac.Extras.Moq/AutoMock.cs index 1d35099..6f66324 100644 --- a/src/Autofac.Extras.Moq/AutoMock.cs +++ b/src/Autofac.Extras.Moq/AutoMock.cs @@ -40,7 +40,8 @@ public class AutoMock : IDisposable { private bool _disposed; - private readonly List _createdServiceTypes = new List(); + private readonly HashSet _createdServiceTypes = new HashSet(); + private readonly HashSet _mockedServiceTypes = new HashSet(); private AutoMock(MockBehavior behavior, Action beforeBuild) : this(new MockRepository(behavior), beforeBuild) @@ -58,7 +59,7 @@ private AutoMock(MockRepository repository, Action beforeBuild // and Moq being last in are least likely to cause ordering conflicts. beforeBuild?.Invoke(builder); - builder.RegisterSource(new MoqRegistrationHandler(_createdServiceTypes)); + builder.RegisterSource(new MoqRegistrationHandler(_createdServiceTypes, _mockedServiceTypes)); this.Container = builder.Build(); @@ -198,8 +199,14 @@ public Mock Mock(params Parameter[] parameters) private T Create(bool isMock, params Parameter[] parameters) { - if (!isMock && !_createdServiceTypes.Contains(typeof(T))) + if (isMock) + { + _mockedServiceTypes.Add(typeof(T)); + } + else + { _createdServiceTypes.Add(typeof(T)); + } return this.Container.Resolve(parameters); } diff --git a/src/Autofac.Extras.Moq/MoqRegistrationHandler.cs b/src/Autofac.Extras.Moq/MoqRegistrationHandler.cs index 51b5aec..d5d883b 100644 --- a/src/Autofac.Extras.Moq/MoqRegistrationHandler.cs +++ b/src/Autofac.Extras.Moq/MoqRegistrationHandler.cs @@ -28,6 +28,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; +using System.Runtime.ExceptionServices; using Autofac.Builder; using Autofac.Core; using Autofac.Features.Metadata; @@ -41,17 +42,20 @@ namespace Autofac.Extras.Moq /// internal class MoqRegistrationHandler : IRegistrationSource { - private readonly IList _createdServiceTypes; + private readonly ISet _createdServiceTypes; + private readonly ISet _mockedServiceTypes; private readonly MethodInfo _createMethod; /// /// Initializes a new instance of the class. /// - /// A list of root services that have been created. - public MoqRegistrationHandler(IList createdServiceTypes) + /// A set of root services that have been created. + /// A set of mocks that have been explicitly configured. + public MoqRegistrationHandler(ISet createdServiceTypes, ISet mockedServiceTypes) { this._createdServiceTypes = createdServiceTypes; + this._mockedServiceTypes = mockedServiceTypes; // This is MockRepository.Create() with zero parameters. This is important because // it limits what can be auto-mocked. @@ -107,7 +111,9 @@ public IEnumerable RegistrationsFor( } else if (ShouldMockService(typedService)) { - if (ServiceCompatibleWithMockRepositoryCreate(typedService)) + // If a mock has been explicitly requested, then always try it. + // This will ensure mocking exceptions get properly thrown. + if (_mockedServiceTypes.Contains(typedService.ServiceType) || ServiceCompatibleWithMockRepositoryCreate(typedService)) { result = RegistrationBuilder.ForDelegate((c, p) => this.CreateMock(c, typedService)) .As(service) @@ -231,9 +237,20 @@ private static bool IsMeta(IServiceWithType typedService) /// private object CreateMock(IComponentContext context, TypedService typedService) { - var specificCreateMethod = this._createMethod.MakeGenericMethod(new[] { typedService.ServiceType }); - var mock = (Mock)specificCreateMethod.Invoke(context.Resolve(), null); - return mock.Object; + try + { + var specificCreateMethod = this._createMethod.MakeGenericMethod(new[] { typedService.ServiceType }); + var mock = (Mock)specificCreateMethod.Invoke(context.Resolve(), null); + return mock.Object; + } + catch (TargetInvocationException ex) + { + // Expose the inner exception as if it was directly thrown. + ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); + + // Won't get here, but the compiler doesn't know that. + throw ex.InnerException; + } } } } diff --git a/test/Autofac.Extras.Moq.Test/AutoMockFixture.cs b/test/Autofac.Extras.Moq.Test/AutoMockFixture.cs index d205d12..d595ee7 100644 --- a/test/Autofac.Extras.Moq.Test/AutoMockFixture.cs +++ b/test/Autofac.Extras.Moq.Test/AutoMockFixture.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Autofac.Core; using Moq; using Xunit; @@ -280,6 +281,38 @@ public void CreateClassWithParameter() } } + [Fact] + public void MockedClassWithConstructorThrows() + { + using (var mock = AutoMock.GetLoose()) + { + mock.Mock().Setup(s => s.DoSomethingExternal()).Returns(7); + Assert.Throws(() => mock.Mock()); + } + } + + public class ClassWithDependency + { + private readonly IDependency _dependency; + + public ClassWithDependency(IDependency dependency) + { + _dependency = dependency; + } + + public int DoSomeThing() => 0; + } + + public class Dependency : IDependency + { + public int DoSomethingExternal() => 0; + } + + public interface IDependency + { + int DoSomethingExternal(); + } + public class ClassWithParameters { public bool InvokedSimpleConstructor { get; } diff --git a/test/Autofac.Extras.Moq.Test/MoqRegistrationHandlerFixture.cs b/test/Autofac.Extras.Moq.Test/MoqRegistrationHandlerFixture.cs index de78062..dcbe6d8 100644 --- a/test/Autofac.Extras.Moq.Test/MoqRegistrationHandlerFixture.cs +++ b/test/Autofac.Extras.Moq.Test/MoqRegistrationHandlerFixture.cs @@ -16,7 +16,7 @@ public class MoqRegistrationHandlerFixture public MoqRegistrationHandlerFixture() { - this._systemUnderTest = new MoqRegistrationHandler(new List()); + this._systemUnderTest = new MoqRegistrationHandler(new HashSet(), new HashSet()); } private interface ISomethingStartable : IStartable @@ -42,8 +42,8 @@ public void RegistrationForConcreteClass_IsHandled() [Fact] public void RegistrationForCreatedType_IsHandled() { - var createdServiceTypes = new List { typeof(TestConcreteClass) }; - var handler = new MoqRegistrationHandler(createdServiceTypes); + var createdServiceTypes = new HashSet { typeof(TestConcreteClass) }; + var handler = new MoqRegistrationHandler(createdServiceTypes, new HashSet()); var registrations = handler.RegistrationsFor(new TypedService(typeof(TestConcreteClass)), s => Enumerable.Empty()); Assert.NotEmpty(registrations);