2010-09-24 12 views
11

Los métodos de extensión no son buenos para las pruebas (que se describe aquí: Mocking Extension Methods with Moq, http://www.clariusconsulting.net/blogs/kzu/archive/2009/12/22/Howtomockextensionmethods.aspx).Cómo simular (con Moq) los métodos de Unity

¿Pero probablemente hay algunas soluciones para burlarse de los métodos de Unity? En mi caso tengo la siguiente función:

public class MyManager 
{ 
    public MyManager(IUnityContainer container) : base(container) { } 

    public IResult DoJob(IData data) 
    { 
     IMyLog log = MyContainer.Resolve<IMyLog>(); 

     ... use log.Id ... 

     MyContainer.Resolve<...>();//usage for other purposes... 
    } 

Quiero estar seguro de que el método 'DoJob' siempre va a obtener el objeto 'IMyLog' del contenedor, pero no de otras fuentes ... ¿cómo podría probar eso?

Mi idea original era cambiar 'DoJob' implementación del método y uso:

IMyLog log = UnityContainer.Resolve(typeof(IMyLog)) as IMyLog; 

Pero 'Resolve (tipo T, ...)' es también un método de extensión ...

Cualquier los pensamientos son bienvenidos

P.S. Tenga en cuenta que ese objeto 'mi registro' se crea lejos de MyManager.DoJob ...

+1

Dependencias en el contenedor IoC son un antipatrón. Intenta restringir el contenedor a una clase de nivel superior y haz que cree tus otros objetos. Como @TheCodeKing sugiere, las fábricas de automóviles pueden ayudar. Consulte http://kozmic.pl/2010/06/20/how-i-use-inversion-of-control-containers/ – TrueWill

Respuesta

3

Guess, encontré la solución más adecuada para la prueba: No es necesario contenedor de la unidad simulada y comprobar si ' log 'object fue tomado de él. Haré una simulación para el objeto 'Log', registraré su instancia de objeto en el contenedor y comprobaré si realmente se usa este objeto de registro.

Esto hará lo que se requiera.

 Mock<IMyLog> mockLog = new Mock<IMyLog>(); 
     mockLog.Setup(mock=>mock.Id).Returns(TestLogId); 

     IUnityContainer container = new UnityContainer(); 
     container 
      .RegisterInstance(mockCommandExecutionLog.Object) 
      ... 
      ; 

     ... 

     mockLog.Verify(
      mock => mock.Id, 
      Times.Once(), 
      "It seems like 'Log' object is not used" 
      ); 

Thanks.

6

Elimina la dependencia de IUnityContainer y las cosas se vuelven mucho más fáciles y más limpias. En cambio, permita que la unidad inyecte sus dependencias que se abstraen en interfaces. Estos son fácilmente burlados. Aquí hay un ejemplo usando un pequeño truco con Unity que inyecta una fábrica automática para IMyLog.

public class MyManager 
{ 
    private readonly Func<IMyLog> logFactory; 

    public MyManager(Func<IMyLog> logFactory) 
    { 
     this.logFactory = logFactory; 
    } 

    public IResult DoJob(IData data) 
    { 
     IMyLog log = logFactory(); 

     ... 
    } 
} 

O si no es necesario para crear la instancia cada vez:

public class MyManager 
{ 
    private readonly IMyLog myLog; 

    public MyManager(IMyLog myLog) 
    { 
     this.myLog = myLog; 
    } 

    public IResult DoJob(IData data) 
    { 
     ... 
    } 
} 
+0

En mi caso, también necesito Unity para otros fines ... (la publicación original se actualizó recientemente) . Probablemente no podría extraer un registro, sino una fábrica de registros y comprobar si se llamó al método 'GetLogObject' ... Gracias por la idea. – Budda

1

Tendré que estar en desacuerdo con ambas respuestas. TheCodeKing, es completamente legítimo usar la interfaz IoC directamente. Un ejemplo podría ser una fábrica de controladores en el proyecto ASP.NET, donde se podría hacer una resolución no trivial usando múltiples métodos en la interfaz IUnityContainer.

Hmm ... con labdas de inyección de fábrica automática, uno nunca tiene que probar la interfaz IoC directamente. Simplemente podría pasar un lambda y verificar que se llame con los parámetros correctos.

Budda, debe nunca llevar un contenedor IoC en su unidad de prueba. Las dependencias deben ser inyectadas manualmente.

A continuación se muestra la solución que uso. Básicamente, creé una capa de abstracción sobre IUnityContainer e implementé una clase simple que delega en IUnityContainer. Debido a que mi interfaz no contiene métodos de extensión, puedo simularlo fácilmente.

public interface IDIContainer { 
    void RegisterType<TFrom>() where TFrom : class; 
    void RegisterType<TFrom, TTo>() where TTo : TFrom; 
    void RegisterType<TFrom, TTo>(string name) where TTo : TFrom; 
    void RegisterType(Type from, Type to); 
    void RegisterType(Type from, Type to, string name); 

    void RegisterInstance<TFrom>(TFrom instance) where TFrom : class; 

    T Resolve<T>(); 
    T Resolve<T>(string name); 
    IEnumerable<T> ResolveAll<T>(); 

    bool IsRegistered<TFrom>(string name) where TFrom : class; 
    bool IsRegistered<TFrom>() where TFrom : class; 
} 


public class DIContainer : IDIContainer { 
    IUnityContainer m_Container = new UnityContainer(); 

    #region IDIContainer Members 

    public void RegisterType<TFrom>() where TFrom : class { 
     m_Container.RegisterType<TFrom>(); 
    } 

    public void RegisterType<TFrom, TTo>() where TTo : TFrom { 
     m_Container.RegisterType<TFrom, TTo>(); 
    } 

    public void RegisterType<TFrom, TTo>(string name) where TTo : TFrom { 
     m_Container.RegisterType<TFrom, TTo>(name); 
    } 

    public void RegisterType(Type from, Type to) { 
     m_Container.RegisterType(from, to); 
    } 

    public void RegisterType(Type from, Type to, string name) { 
     m_Container.RegisterType(from, to, name); 
    } 

    public void RegisterInstance<TFrom>(TFrom instance) where TFrom : class { 
     m_Container.RegisterInstance<TFrom>(instance); 
    } 

    public T Resolve<T>() { 
     return m_Container.Resolve<T>(); 
    } 

    public IEnumerable<T> ResolveAll<T>() { 
     return m_Container.ResolveAll<T>(); 
    } 

    public T Resolve<T>(string name) { 
     return m_Container.Resolve<T>(name); 
    } 

    public bool IsRegistered<TFrom>(string name) where TFrom : class { 
     return m_Container.IsRegistered<TFrom>(name); 
    } 

    public bool IsRegistered<TFrom>() where TFrom : class { 
     return m_Container.IsRegistered<TFrom>(); 
    } 

    #endregion 
} 

Ahora, reescribir su clase para utilizar IDIContainer:

public class MyManager 
{ 
    public MyManager(IDIContainer container) : base(container) { } 

    public IResult DoJob(IData data) 
    { 
     IMyLog log = MyContainer.Resolve<IMyLog>(); 

     ... use log.Id ... 

     MyContainer.Resolve<...>();//usage for other purposes... 
    } 
} 

y reescribir la prueba unitaria de este modo:

[TestClass] 
public class Test { 
    [TestMethod] 
    public void TestDoJob() { 
    Mock<IMyLog> mockLog = new Mock<IMyLog>(); 
    Mock<IDIContainer> containerMock = new Mock<IDIContainer>(); 

    //Setup mock container to return a log mock we set up earlier 
    containerMock.Setup(c=>c.Resolve<IMyLog>()),Returns(mockLog); 
    //Verify that all setups have been performed 
    containerMock.VerifyAll(); 
    } 
} 
+0

No estaba diciendo que no sea legítimo usar el IoC, solo que es más fácil probarlo con dependencias directas. El escenario de fábrica fue usado realmente en mi ejemplo. Se puede lograr utilizando la función de fábrica automática de Unity sin la necesidad de hacer referencia al IoC. – TheCodeKing

+0

Igor, gracias, es una muy buena solución. ¡Gracias! – Budda

+0

@TheCodeKing Simplemente vuelve a leer tu publicación. No sabía sobre el soporte para la fábrica de automóviles. Junto con 'InjectionFactory', puede eliminar la necesidad de inyectar el contenedor. Lo tomo todo de vuelta :) –

6

Sé que llego tarde a la fiesta, pero yo tuvo el mismo problema y lo resolvió burlándose del siguiente método:

IUnityContainer.Resolve(Type t, string name, params ResolverOverride[] resolverOverrides); 

Por ejemplo -

unityMock = new Mock<IUnityContainer>(MockBehavior.Strict); 
validatorMock = new Mock<IValidator>(MockBehavior.Strict); 
unityMock.Setup(p => p.Resolve(typeof(IValidator), null)) 
    .Returns(validatorMock.Object); 

Sólo en caso de que alguien necesita para burlarse de esto y no se puede eliminar la dependencia en el envase.

Cuestiones relacionadas