2010-09-24 16 views
11

I metodi di estensione non sono validi per il test (che è descritto qui: Mocking Extension Methods with Moq, http://www.clariusconsulting.net/blogs/kzu/archive/2009/12/22/Howtomockextensionmethods.aspx).Come simulare (con Moq) Metodi di unità

Ma probabilmente ci sono alcune soluzioni per la simulazione dei metodi Unity? Nel mio caso ho la seguente funzione:

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... 
    } 

io voglio essere sicuro che il metodo 'DoJob' sarà sempre ottenere l'oggetto 'IMyLog' dal contenitore, ma non da altre fonti ... come potrei provare questo?

La mia idea originale era quella di cambiare implementazione 'DoJob' metodo ed impiego:

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

Ma 'Resolve (Type t, ...)' è anche un metodo di estensione ...

Qualsiasi i pensieri sono ben accetti

P.S. Si noti che l'oggetto "My Log" viene creato lontano da MyManager.DoJob ...

+1

Le dipendenze sul contenitore IoC sono un antipattern. Prova a limitare il contenitore a una classe di livello superiore e fargli creare altri oggetti. Come suggerisce @TheCodeKing, le auto-fabbriche possono aiutare. Vedi http://kozmic.pl/2010/06/20/how-i-use-inversion-of-control-containers/ – TrueWill

risposta

3

Guess, ho trovato la soluzione più appropriata per la prova: non è necessario contenitore unità finta e verificare se ' log 'oggetto è stato preso da esso. Creerò solo un oggetto per l'oggetto "Log", registrerò la sua istanza di oggetto nel container e verificherò se questo oggetto log è realmente utilizzato.

Questo farà ciò che è richiesto.

 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" 
      ); 

Grazie.

6

Rimuovere la dipendenza da IUnityContainer e le cose diventano molto più semplici e pulite. Invece lascia che l'unità inietti le tue dipendenze che sono astratte in interfacce. Questi sono facilmente derisi. Ecco un esempio che utilizza un piccolo trucco con Unity che inietta un auto-factory per 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(); 

     ... 
    } 
} 

Oppure, se non è necessario per creare l'istanza di volta in volta:

public class MyManager 
{ 
    private readonly IMyLog myLog; 

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

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

Nel mio caso ho bisogno di Unity anche per altri scopi ... (post originale è stato aggiornato di recente) . Probabilmente non potrei estrarre un log ma una log-factory e controllare se è stato chiamato il metodo 'GetLogObject' ... Grazie per l'idea. – Budda

1

Dovrò essere in disaccordo con entrambe le risposte. TheCodeKing, è del tutto legittimo utilizzare direttamente l'interfaccia IoC. Un esempio potrebbe essere una fabbrica di controller nel progetto ASP.NET - dove si potrebbe fare una risoluzione non banale usando più metodi nell'interfaccia IUnityContainer.

Hmm ... con labdas per l'iniezione automatica, non è mai necessario testare l'interfaccia IoC direttamente. Potresti semplicemente passare un lambda e verificare che venga chiamato con i parametri corretti.

Budda, si dovrebbe mai inserire un contenitore di IoC nel test dell'unità. Le dipendenze devono essere iniettate manualmente.

Di seguito è la soluzione che utilizzo. Fondamentalmente ho creato uno strato di astrazione su IUnityContainer e ho implementato una semplice classe che delega a IUnityContainer. Poiché la mia interfaccia non contiene metodi di estensione, posso facilmente deriderla.

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 
} 

Ora, riscrivere la classe da utilizzare 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... 
    } 
} 

e riscrivere il test di unità in questo 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

Non stavo dicendo che non è legittimo usare l'IoC, solo che ha reso più facile testare con dipendenze dirette. Lo scenario di fabbrica è stato effettivamente utilizzato nel mio esempio. Può essere ottenuto utilizzando la funzione di auto-factory di Unity senza la necessità di fare riferimento a IoC. – TheCodeKing

+0

Igor, grazie, è davvero una buona soluzione. Grazie! – Budda

+0

@TheCodeKing Rileggi il tuo post. Non sapevo del supporto per l'auto-fabbrica. Accoppiato con 'InjectionFactory' si potrebbe evitare di iniettare il contenitore. Mi riprendo tutto :) –

6

So di essere in ritardo alla festa, ma io ha avuto lo stesso problema e lo ha risolto prendendo in giro il seguente metodo:

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

Per esempio -

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

Solo nel caso qualcuno ha bisogno di prendere in giro questo fuori e non può rimuovere la dipendenza sul contenitore.

Problemi correlati