Skip to content

Commit

Permalink
Merge pull request #34 from alistairjevans/throw-correct-exception
Browse files Browse the repository at this point in the history
Explicitly state that mock has been requested
  • Loading branch information
tillig authored Mar 22, 2020
2 parents 819519b + c2073ec commit 6290a95
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 13 deletions.
13 changes: 10 additions & 3 deletions src/Autofac.Extras.Moq/AutoMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public class AutoMock : IDisposable
{
private bool _disposed;

private readonly List<Type> _createdServiceTypes = new List<Type>();
private readonly HashSet<Type> _createdServiceTypes = new HashSet<Type>();
private readonly HashSet<Type> _mockedServiceTypes = new HashSet<Type>();

private AutoMock(MockBehavior behavior, Action<ContainerBuilder> beforeBuild)
: this(new MockRepository(behavior), beforeBuild)
Expand All @@ -58,7 +59,7 @@ private AutoMock(MockRepository repository, Action<ContainerBuilder> 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();

Expand Down Expand Up @@ -198,8 +199,14 @@ public Mock<T> Mock<T>(params Parameter[] parameters)

private T Create<T>(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<T>(parameters);
}
Expand Down
31 changes: 24 additions & 7 deletions src/Autofac.Extras.Moq/MoqRegistrationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -41,17 +42,20 @@ namespace Autofac.Extras.Moq
/// </summary>
internal class MoqRegistrationHandler : IRegistrationSource
{
private readonly IList<Type> _createdServiceTypes;
private readonly ISet<Type> _createdServiceTypes;
private readonly ISet<Type> _mockedServiceTypes;

private readonly MethodInfo _createMethod;

/// <summary>
/// Initializes a new instance of the <see cref="MoqRegistrationHandler"/> class.
/// </summary>
/// <param name="createdServiceTypes">A list of root services that have been created.</param>
public MoqRegistrationHandler(IList<Type> createdServiceTypes)
/// <param name="createdServiceTypes">A set of root services that have been created.</param>
/// <param name="mockedServiceTypes">A set of mocks that have been explicitly configured.</param>
public MoqRegistrationHandler(ISet<Type> createdServiceTypes, ISet<Type> mockedServiceTypes)
{
this._createdServiceTypes = createdServiceTypes;
this._mockedServiceTypes = mockedServiceTypes;

// This is MockRepository.Create<T>() with zero parameters. This is important because
// it limits what can be auto-mocked.
Expand Down Expand Up @@ -107,7 +111,9 @@ public IEnumerable<IComponentRegistration> 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)
Expand Down Expand Up @@ -231,9 +237,20 @@ private static bool IsMeta(IServiceWithType typedService)
/// </returns>
private object CreateMock(IComponentContext context, TypedService typedService)
{
var specificCreateMethod = this._createMethod.MakeGenericMethod(new[] { typedService.ServiceType });
var mock = (Mock)specificCreateMethod.Invoke(context.Resolve<MockRepository>(), null);
return mock.Object;
try
{
var specificCreateMethod = this._createMethod.MakeGenericMethod(new[] { typedService.ServiceType });
var mock = (Mock)specificCreateMethod.Invoke(context.Resolve<MockRepository>(), 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;
}
}
}
}
33 changes: 33 additions & 0 deletions test/Autofac.Extras.Moq.Test/AutoMockFixture.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac.Core;
using Moq;
using Xunit;

Expand Down Expand Up @@ -280,6 +281,38 @@ public void CreateClassWithParameter()
}
}

[Fact]
public void MockedClassWithConstructorThrows()
{
using (var mock = AutoMock.GetLoose())
{
mock.Mock<IDependency>().Setup(s => s.DoSomethingExternal()).Returns(7);
Assert.Throws<DependencyResolutionException>(() => mock.Mock<ClassWithDependency>());
}
}

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; }
Expand Down
6 changes: 3 additions & 3 deletions test/Autofac.Extras.Moq.Test/MoqRegistrationHandlerFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class MoqRegistrationHandlerFixture

public MoqRegistrationHandlerFixture()
{
this._systemUnderTest = new MoqRegistrationHandler(new List<Type>());
this._systemUnderTest = new MoqRegistrationHandler(new HashSet<Type>(), new HashSet<Type>());
}

private interface ISomethingStartable : IStartable
Expand All @@ -42,8 +42,8 @@ public void RegistrationForConcreteClass_IsHandled()
[Fact]
public void RegistrationForCreatedType_IsHandled()
{
var createdServiceTypes = new List<Type> { typeof(TestConcreteClass) };
var handler = new MoqRegistrationHandler(createdServiceTypes);
var createdServiceTypes = new HashSet<Type> { typeof(TestConcreteClass) };
var handler = new MoqRegistrationHandler(createdServiceTypes, new HashSet<Type>());
var registrations = handler.RegistrationsFor(new TypedService(typeof(TestConcreteClass)), s => Enumerable.Empty<IComponentRegistration>());

Assert.NotEmpty(registrations);
Expand Down

0 comments on commit 6290a95

Please sign in to comment.