Created
July 20, 2010 15:24
-
-
Save jeffdeville/483100 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Autofac; | |
using Autofac.Builder; | |
using Moq; | |
using System.Reflection; | |
using Autofac.Component.Activation; | |
using Autofac.Component; | |
using Autofac.Component.Scope; | |
namespace yellowbook.testing.common | |
{ | |
/// <summary> | |
/// Auto-mocking factory that can create an instance of the | |
/// class under test and automatically inject mocks for all its dependencies. | |
/// </summary> | |
/// <remarks> | |
/// Note that only interface dependencies will be mocked for now. | |
/// </remarks> | |
public class AutoMockContainer : IResolve | |
{ | |
IContainer container; | |
MockFactory factory; | |
/// <summary> | |
/// Initializes the container with the <see cref="MockFactory"/> that | |
/// will be used to create dependent mocks. | |
/// </summary> | |
public AutoMockContainer(MockFactory factory) | |
{ | |
this.factory = factory; | |
var builder = new ContainerBuilder(); | |
builder.Register(this).OwnedByContainer(); | |
this.container = builder.Build(); | |
this.container.AddRegistrationSource(new MoqRegistrationSource(this.factory)); | |
} | |
/// <summary> | |
/// Gets or creates a mock for the given type, with | |
/// the default behavior specified by the factory. | |
/// </summary> | |
public Mock<T> GetMock<T>() | |
where T : class | |
{ | |
// TODO: support passing a different MockBehavior ? | |
var obj = (IMocked<T>)this.Create<T>(); | |
return obj.Mock; | |
} | |
/// <summary> | |
/// Creates an instance of a class under test, | |
/// injecting all necessary dependencies as mocks. | |
/// </summary> | |
/// <typeparam name="T">Requested object type.</typeparam> | |
public T Create<T>() where T : class | |
{ | |
if (!container.IsRegistered<T>()) | |
{ | |
var builder = new ContainerBuilder(); | |
builder.Register<T>(); | |
builder.Build(container); | |
} | |
return container.Resolve<T>(); | |
} | |
/// <summary> | |
/// Creates an instance of a class under test using | |
/// the given activator function. | |
/// </summary> | |
/// <typeparam name="T">Requested object type.</typeparam> | |
public T Create<T>(Func<IResolve, T> activator) where T : class | |
{ | |
if (!container.IsRegistered<T>()) | |
{ | |
var builder = new ContainerBuilder(); | |
builder.Register<T>(c => activator(this)); | |
builder.Build(container); | |
} | |
return container.Resolve<T>(); | |
} | |
/// <summary> | |
/// Registers and resolves the given service on the container. | |
/// </summary> | |
/// <typeparam name="TService">Service</typeparam> | |
/// <typeparam name="TImplementation">The implementation of the service.</typeparam> | |
public void Register<TService, TImplementation>() | |
{ | |
var builder = new ContainerBuilder(); | |
builder.Register<TImplementation>().As<TService>().ContainerScoped(); | |
builder.Build(container); | |
// TODO: why would you need back the instance you have just registered? | |
// Isn't it supposed to be automatically injected from now on? | |
//return Resolve<TService>(); | |
} | |
/// <summary> | |
/// Registers the given service instance on the container. | |
/// </summary> | |
/// <typeparam name="TService">Service type.</typeparam> | |
/// <param name="instance">Service instance.</param> | |
public void Register<TService>(TService instance) | |
{ | |
var builder = new ContainerBuilder(); | |
builder.Register(instance).As<TService>(); | |
builder.Build(container); | |
} | |
/// <summary> | |
/// Registers the given service creation delegate on the container. | |
/// </summary> | |
/// <typeparam name="TService">Service type.</typeparam> | |
public void Register<TService>(Func<IResolve, TService> activator) | |
{ | |
var builder = new ContainerBuilder(); | |
builder.Register(c => activator(this)).As<TService>(); | |
builder.Build(container); | |
} | |
/// <summary> | |
/// Resolves a required service of the given type. | |
/// </summary> | |
public T Resolve<T>() | |
{ | |
return container.Resolve<T>(); | |
} | |
/// <summary> | |
/// Resolves unknown interfaces and Mocks using | |
/// the <see cref="MockFactory"/> from the scope. | |
/// </summary> | |
class MoqRegistrationSource : IRegistrationSource | |
{ | |
private readonly MockFactory factory; | |
private readonly MethodInfo createMethod; | |
public MoqRegistrationSource(MockFactory factory) | |
{ | |
this.factory = factory; | |
this.createMethod = factory.GetType().GetMethod("Create", new Type[] { }); | |
} | |
/// <summary> | |
/// Retrieve a registration for an unregistered service, to be used | |
/// by the container. | |
/// </summary> | |
/// <param name="service">The service that was requested.</param> | |
/// <param name="registration">A registration providing the service.</param> | |
/// <returns> | |
/// True if the registration could be created. | |
/// </returns> | |
public bool TryGetRegistration | |
(Service service, out IComponentRegistration registration) | |
{ | |
if (service == null) | |
throw new ArgumentNullException("service"); | |
registration = null; | |
var typedService = service as TypedService; | |
if ((typedService == null) || (!typedService.ServiceType.IsInterface)) | |
return false; | |
var descriptor = new Descriptor( | |
new UniqueService(), | |
new[] { service }, | |
typedService.ServiceType); | |
registration = new Registration( | |
descriptor, | |
new DelegateActivator | |
((c, p) => | |
{ | |
var specificCreateMethod = this.createMethod.MakeGenericMethod(new[] { typedService.ServiceType }); | |
var mock = (Mock)specificCreateMethod.Invoke(factory, null); | |
return mock.Object; | |
}), | |
new ContainerScope(), | |
InstanceOwnership.Container); | |
return true; | |
} | |
} | |
} | |
/// <summary> | |
/// Interface implemented by the <see cref="AutoMockContainer"/> so that | |
/// the <c>Register</c> overloads can receive a creation | |
/// function for the service, rather than just a type. | |
/// </summary> | |
public interface IResolve | |
{ | |
/// <summary> | |
/// Resolves a required service of the given type. | |
/// </summary> | |
T Resolve<T>(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment