2010-03-17 9 views
9

J'ai un constructeur qui a une dépendance non-interface:AutoMockContainer avec le soutien pour les classes de automocking avec dépendances non l'interface

public MainWindowViewModel(IWorkItemProvider workItemProvider, WeekNavigatorViewModel weekNavigator) 

J'utilise le automockcontainer Moq.Contrib. Si j'essaye d'automock la classe MainWindowViewModel, j'obtiens une erreur due à la dépendance de WeekNavigatorViewModel.

Existe-t-il des conteneurs d'automatisation qui prennent en charge les types non-interface?

Comme l'a montré Mark ci-dessous; Oui, vous pouvez! :-) J'ai remplacé le Moq.Contrib AutoMockContainer par le contenu que Mark présente dans sa réponse, la seule différence est que les mocks générés automatiquement sont enregistrés en tant que singletons, mais vous pouvez le rendre configurable. Voici la solution finale:

/// <summary> 
/// Auto-mocking factory that can create an instance of the 
/// class under test and automatically inject mocks for all its dependencies. 
/// </summary> 
/// <remarks> 
/// Mocks interface and class dependencies 
/// </remarks> 
public class AutoMockContainer 
{ 
    readonly IContainer _container; 

    public AutoMockContainer(MockFactory factory) 
    { 
     var builder = new ContainerBuilder(); 

     builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource()); 
     builder.RegisterSource(new MoqRegistrationSource(factory)); 

     _container = builder.Build(); 
    } 

    /// <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 
    { 
     return (_container.Resolve<T>() as IMocked<T>).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 
    { 
     return _container.Resolve<T>(); 
    } 

    public T Resolve<T>() 
    { 
     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.RegisterType<TImplementation>().As<TService>().SingleInstance(); 
     builder.Update(_container); 
    } 

    /// <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(); 

     if (instance.GetType().IsClass) 
      builder.RegisterInstance(instance as object).As<TService>(); 
     else 
      builder.Register(c => instance).As<TService>(); 

     builder.Update(_container); 
    } 

    class MoqRegistrationSource : IRegistrationSource 
    { 
     private readonly MockFactory _factory; 
     private readonly MethodInfo _createMethod; 

     public MoqRegistrationSource(MockFactory factory) 
     { 
      _factory = factory; 
      _createMethod = factory.GetType().GetMethod("Create", new Type[] { }); 
     } 

     public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) 
     { 
      var swt = service as IServiceWithType; 
      if (swt == null) 
      { 
       yield break; 
      } 

      if (!swt.ServiceType.IsInterface) 
       yield break; 

      var existingReg = registrationAccessor(service); 
      if (existingReg.Any()) 
      { 
       yield break; 
      } 

      var reg = RegistrationBuilder.ForDelegate((c, p) => 
      { 
       var createMethod = _createMethod.MakeGenericMethod(swt.ServiceType); 
       return ((Mock)createMethod.Invoke(_factory, null)).Object; 
      }).As(swt.ServiceType).SingleInstance().CreateRegistration(); 

      yield return reg; 
     } 

     public bool IsAdapterForIndividualComponents 
     { 
      get { return false; } 
     } 
    } 
} 

Répondre

16

Vous pouvez assez facilement écrire vous-même que si vous utilisez un DI Container qui prend en charge la résolution juste à temps des types demandés.

J'ai récemment écrit un prototype pour exactement ce but en utilisant Autofac et Moq, mais d'autres conteneurs pourraient être utilisés à la place.

Voici le IRegistrationSource approprié:

public class AutoMockingRegistrationSource : IRegistrationSource 
{ 
    private readonly MockFactory mockFactory; 

    public AutoMockingRegistrationSource() 
    { 
     this.mockFactory = new MockFactory(MockBehavior.Default); 
     this.mockFactory.CallBase = true; 
     this.mockFactory.DefaultValue = DefaultValue.Mock; 
    } 

    public MockFactory MockFactory 
    { 
     get { return this.mockFactory; } 
    } 

    #region IRegistrationSource Members 

    public IEnumerable<IComponentRegistration> RegistrationsFor(
     Service service, 
     Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor) 
    { 
     var swt = service as IServiceWithType; 
     if (swt == null) 
     { 
      yield break; 
     } 

     var existingReg = registrationAccessor(service); 
     if (existingReg.Any()) 
     { 
      yield break; 
     } 

     var reg = RegistrationBuilder.ForDelegate((c, p) => 
      { 
       var createMethod = 
        typeof(MockFactory).GetMethod("Create", Type.EmptyTypes).MakeGenericMethod(swt.ServiceType); 
       return ((Mock)createMethod.Invoke(this.MockFactory, null)).Object; 
      }).As(swt.ServiceType).CreateRegistration(); 

     yield return reg; 
    } 

    #endregion 
} 

Vous pouvez maintenant mettre en place le récipient dans un test unitaire comme celui-ci:

[TestMethod] 
public void ContainerCanCreate() 
{ 
    // Fixture setup 
    var builder = new ContainerBuilder(); 
    builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource()); 
    builder.RegisterSource(new AutoMockingRegistrationSource()); 
    var container = builder.Build(); 
    // Exercise system 
    var result = container.Resolve<MyClass>(); 
    // Verify outcome 
    Assert.IsNotNull(result); 
    // Teardown 
} 

C'est tout ce que vous avez besoin pour commencer.

MyClass est une classe concrète avec une dépendance abstraite. Voici la signature du constructeur:

public MyClass(ISomeInterface some) 

Notez que vous ne devez pas utiliser Autofac (ou tout autre récipient DI) dans votre code de production.

+0

Est-ce que cela fonctionne si MyClass a une dépendance sur un type concret au lieu d'une interface? – Marius

+0

Oui, 'AnyConcreteTypeNotAlreadyRegisteredSource' d'Autofac s'occupe de la résolution des types concrets. –

+0

Je pense que l'enregistrement Mock devrait être explicitement défini comme 'SingleInstance' dans cette instruction' As (swt.ServiceType) .CreateRegistration() '. J'ai eu un problème avec NSubstitute, si ce n'est pas 'SingleInstance' il pourrait être problématique. Par exemple: 'Resolver.Resolve (). Get (" 1 "). Returns (nouveau Basket (1, 50));' ici 'ICacheManager' créé par conteneur auto-substitué et s'il est transitoire' Retourne 'ne sera jamais défini comme prévu car il s'agit d'une nouvelle instance utilisant' var sut = Resolver.Resolve (); 'ici, IOrderService utilise une nouvelle dépendance' ICacheManager' substituée. –