2015-12-03 28 views
5

Utilizzando AutoFixture con il pacchetto AutoFixture.AutoMoq, a volte mi trovo prove che non sono stati configurati per testare correttamente la cosa intendevano mettere alla prova, ma il problema non è mai stato scoperto a causa del default (allentato) comportamento Mock:Come posso rendere AutoMoqCustomization utilizzare Strict MockBehavior?

public interface IService 
{ 
    bool IsSomethingTrue(int id); 
} 

void Main() 
{ 
    var fixture = new Fixture() 
     .Customize(new AutoMoqCustomization()); 
    var service = fixture.Freeze<Mock<IService>>(); 
    Console.WriteLine(service.Object.IsSomethingTrue(1)); // false 
} 

Mi piacerebbe creare Mock creati con un comportamento rigoroso, quindi siamo costretti a chiamare Setup() per i metodi che ci aspettiamo di essere chiamati. Posso fare questo per ogni singolo finto in questo modo:

fixture.Customize<Mock<IService>>(c => c.FromFactory(() => new Mock<IService>(MockBehavior.Strict))); 

Ma dopo la pettinatura attraverso il codice sorgente per AutoMoqCustomization() e le varie ISpecimenBuilder e altre implementazioni, sono abbastanza perso per quanto riguarda il modo migliore per fare proprio tutto I mazzi vengono inizializzati con un comportamento rigoroso. Il framework sembra essere molto flessibile ed estensibile, quindi sono sicuro che c'è un modo semplice per farlo: non riesco a capire come.

risposta

3

Non c'è una semplice funzione integrata che ti permetta di fare qualcosa del genere, ma non dovrebbe essere che il sia difficile da fare.

In sostanza, è necessario modificare MockConstructorQuery in modo da richiamare il costruttore che assume un valore MockBehavior e passare MockBehavior.Strict.

Ora, non si può cambiamento che il comportamento in MockConstructorQuery, ma che classe è solo alcuni 9-10 righe di codice, così si dovrebbe essere in grado di creare una nuova classe che implementa IMethodQuery utilizzando MockConstructorQuery come un punto di partenza punto.

Allo stesso modo, avrete anche bisogno di creare un custom ICustomization che fa quasi esattamente la stessa di AutoMoqCustomization, con l'unica eccezione che utilizza la vostra abitudine IMethodQuery con configurazione finta rigorosa invece di MockConstructorQuery. Ecco altre 7 righe di codice che dovrai scrivere.

Tutto ciò che ho detto, secondo la mia esperienza, utilizzando mittenti rigorosi è una cattiva idea. Renderà i tuoi test fragili e perderai un sacco di tempo per riparare i test "guasti". Posso solo raccomandare che tu non lo faccia, ma ora ti ho avvertito; è il tuo piede

+0

Grazie per il feedback. Vedrò se riesco a farlo funzionare. Riguardo alle stronzate severe: se i burloni fossero veramente "schifosi", penso che il tuo punto sia valido. Sfortunatamente, la maggior parte dei miei mock in realtà servono allo scopo di * stub *, che dovrebbero restituire un valore. Se il SUT si basa sui dati restituiti dallo stub e non ho impostato lo stub, nel migliore dei casi il test fallirà e nel peggiore dei casi il test passerà per il motivo sbagliato. Preferirei, in entrambi i casi, vedere immediatamente la riga di codice che mostra quale stub deve essere configurato, piuttosto che dover dedurre da dove viene la NRE. – StriplingWarrior

+0

@StriplingWarrior http://blog.ploeh.dk/2013/10/23/mocks-for-commands-stubs-for-queries –

+0

Trovo che l'esempio dell'articolo sia piuttosto elaborato: rompere CQS per creare e restituire un oggetto utente che non ha nemmeno l'ID fornito per il metodo GetUser.Questi non sono i tipi di modifiche che in genere vedo essere apportate ai metodi. Le modifiche che vedo regolarmente richiederebbero comunque un cambio nel test unitario, anche se sono state scritte come l'ultimo esempio corretto in quell'articolo. Quando ciò accade, trovo che il comportamento fail-fast di Strict mock fa risparmiare un sacco di tempo e, cosa più importante, aiuta a garantire che i miei test unitari stiano testando ciò che affermano di testare. – StriplingWarrior

0

Per chi fosse interessato, in basso è possibile trovare la risposta di @ MarkSeemann tradotta nel codice. Sono abbastanza sicuro che non copra tutti i casi d'uso e non sia stato pesantemente testato. Ma dovrebbe essere un buon punto di partenza.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Reflection; 
using Moq; 
using Ploeh.AutoFixture; 
using Ploeh.AutoFixture.AutoMoq; 
using Ploeh.AutoFixture.Kernel; 

namespace ConsoleApplication1 
{ 
    public class StrictAutoMoqCustomization : ICustomization 
    { 
     public StrictAutoMoqCustomization() : this(new MockRelay()) { } 

     public StrictAutoMoqCustomization(ISpecimenBuilder relay) 
     { 
      // TODO Null check params 
      Relay = relay; 
     } 

     public ISpecimenBuilder Relay { get; } 

     public void Customize(IFixture fixture) 
     { 
      // TODO Null check params 
      fixture.Customizations.Add(new MockPostprocessor(new MethodInvoker(new StrictMockConstructorQuery()))); 
      fixture.ResidueCollectors.Add(Relay); 
     } 
    } 

    public class StrictMockConstructorMethod : IMethod 
    { 
     private readonly ConstructorInfo ctor; 
     private readonly ParameterInfo[] paramInfos; 

     public StrictMockConstructorMethod(ConstructorInfo ctor, ParameterInfo[] paramInfos) 
     { 
      // TODO Null check params 
      this.ctor = ctor; 
      this.paramInfos = paramInfos; 
     } 

     public IEnumerable<ParameterInfo> Parameters => paramInfos; 

     public object Invoke(IEnumerable<object> parameters) => ctor.Invoke(parameters?.ToArray() ?? new object[] { }); 
    } 

    public class StrictMockConstructorQuery : IMethodQuery 
    { 
     public IEnumerable<IMethod> SelectMethods(Type type) 
     { 
      if (!IsMock(type)) 
      { 
       return Enumerable.Empty<IMethod>(); 
      } 

      if (!GetMockedType(type).IsInterface && !IsDelegate(type)) 
      { 
       return Enumerable.Empty<IMethod>(); 
      } 

      var ctor = type.GetConstructor(new[] { typeof(MockBehavior) }); 

      return new IMethod[] 
      { 
       new StrictMockConstructorMethod(ctor, ctor.GetParameters()) 
      }; 
     } 

     private static bool IsMock(Type type) 
     { 
      return type != null && type.IsGenericType && typeof(Mock<>).IsAssignableFrom(type.GetGenericTypeDefinition()) && !GetMockedType(type).IsGenericParameter; 
     } 

     private static Type GetMockedType(Type type) 
     { 
      return type.GetGenericArguments().Single(); 
     } 

     internal static bool IsDelegate(Type type) 
     { 
      return typeof(MulticastDelegate).IsAssignableFrom(type.BaseType); 
     } 
    } 
} 

Uso

var fixture = new Fixture().Customize(new StrictAutoMoqCustomization());