2012-02-18 6 views
6

ho incontrato una classe durante il mio lavoro che assomiglia a questo:Creazione di un ibrido di un oggetto di simulazione e anonimo utilizzando ad es. Moq e AutoFixture?

public class MyObject 
{ 
    public int? A {get; set;} 
    public int? B {get; set;} 
    public int? C {get; set;} 
    public virtual int? GetSomeValue() 
    { 
    //simplified behavior: 
    return A ?? B ?? C; 
    } 
} 

Il problema è che ho un po 'di codice che accede A, B e C e chiama il metodo GetSomeValue() (ora, direi questo non è un buon design, ma a volte le mie mani sono legate ;-)). Voglio creare una simulazione di questo oggetto, che, allo stesso tempo, ha A, B e C impostati su alcuni valori. Così, quando uso moq come tale:

var m = new Mock<MyObject>() { DefaultValue = DefaultValue.Mock }; 

mi permette di impostare un risultato su GetSomeValue() metodo, ma tutte le proprietà sono impostate su null (e la creazione di tutti loro con il programma di installazione() è abbastanza ingombrante, poiché l'oggetto reale è un brutto oggetto dati e ha più proprietà rispetto all'esempio semplificato sopra).

Così d'altra parte, utilizzando AutoFixture come questo:

var fixture = new Fixture(); 
var anyMyObject = fixture.CreateAnonymous<MyObject>(); 

mi lascia senza la capacità di Stup una chiamata al metodo GetSomeValue().

C'è un modo per combinare i due, per avere valori anonimi e la possibilità di impostare i risultati delle chiamate?

Modifica

in base alla risposta del nemesv, ho derivato il seguente metodo di utilità (spero capito bene):

public static Mock<T> AnonymousMock<T>() where T : class 
{ 
    var mock = new Mock<T>(); 
    fixture.Customize<T>(c => c.FromFactory(() => mock.Object)); 
    fixture.CreateAnonymous<T>(); 
    fixture.Customizations.RemoveAt(0); 
    return mock; 
} 

risposta

5

Questo è effettivamente possibile fare con AutoFixture, ma richiede un po 'di tweaking. I punti di estensibilità sono tutti presenti, ma ammetto che in questo caso la soluzione non è particolarmente rilevabile.

Diventa ancora più difficile se si desidera che funzioni con tipi nidificati/complessi.

Data la classe di MyObject sopra, così come questo MyParent classe:

public class MyParent 
{ 
    public MyObject Object { get; set; } 

    public string Text { get; set; } 
} 

queste unità di test tutti passano:

public class Scenario 
{ 
    [Fact] 
    public void CreateMyObject() 
    { 
     var fixture = new Fixture().Customize(new MockHybridCustomization()); 

     var actual = fixture.CreateAnonymous<MyObject>(); 

     Assert.NotNull(actual.A); 
     Assert.NotNull(actual.B); 
     Assert.NotNull(actual.C); 
    } 

    [Fact] 
    public void MyObjectIsMock() 
    { 
     var fixture = new Fixture().Customize(new MockHybridCustomization()); 

     var actual = fixture.CreateAnonymous<MyObject>(); 

     Assert.NotNull(Mock.Get(actual)); 
    } 

    [Fact] 
    public void CreateMyParent() 
    { 
     var fixture = new Fixture().Customize(new MockHybridCustomization()); 

     var actual = fixture.CreateAnonymous<MyParent>(); 

     Assert.NotNull(actual.Object); 
     Assert.NotNull(actual.Text); 
     Assert.NotNull(Mock.Get(actual.Object)); 
    } 

    [Fact] 
    public void MyParentIsMock() 
    { 
     var fixture = new Fixture().Customize(new MockHybridCustomization()); 

     var actual = fixture.CreateAnonymous<MyParent>(); 

     Assert.NotNull(Mock.Get(actual)); 
    } 
} 

Cosa c'è in MockHybridCustomization? Questo:

public class MockHybridCustomization : ICustomization 
{ 
    public void Customize(IFixture fixture) 
    { 
     fixture.Customizations.Add(
      new MockPostprocessor(
       new MethodInvoker(
        new MockConstructorQuery()))); 
     fixture.Customizations.Add(
      new Postprocessor(
       new MockRelay(t => 
        t == typeof(MyObject) || t == typeof(MyParent)), 
       new AutoExceptMoqPropertiesCommand().Execute, 
       new AnyTypeSpecification())); 
    } 
} 

Il MockPostprocessor, MockConstructorQuery e MockRelay classi sono definiti nella AutoMoq extension a AutoFixture, quindi avrai bisogno di aggiungere un riferimento a questa libreria. Tuttavia, si noti che non è necessario aggiungere lo AutoMoqCustomization.

La classe AutoExceptMoqPropertiesCommand è anche costruito su misura per l'occasione:

public class AutoExceptMoqPropertiesCommand : AutoPropertiesCommand<object> 
{ 
    public AutoExceptMoqPropertiesCommand() 
     : base(new NoInterceptorsSpecification()) 
    { 
    } 

    protected override Type GetSpecimenType(object specimen) 
    { 
     return specimen.GetType(); 
    } 

    private class NoInterceptorsSpecification : IRequestSpecification 
    { 
     public bool IsSatisfiedBy(object request) 
     { 
      var fi = request as FieldInfo; 
      if (fi != null) 
      { 
       if (fi.Name == "__interceptors") 
        return false; 
      } 

      return true; 
     } 
    } 
} 

Questa soluzione fornisce una soluzione generale al problema. Tuttavia, non è stato ampiamente testato, quindi mi piacerebbe avere un feedback su di esso.

+0

Questa è una bella soluzione, soprattutto perché copre tipi nidificati, tuttavia, per essere più generale, con i tipi nidificati, c'è qualche modo per sbarazzarsi della parte: t == typeof (MyObject) | | t == typeof (MyParent)? La logica che sto pensando è qualcosa del tipo: "se il tipo annidato dell'ibrido è mockable, rendilo un ibrido, altrimenti rendilo un valore anonimo". –

+0

Sì, basta sostituire il predicato con qualcosa di più generale. –

+0

Grazie! Ho provato ad usare un predicato di (t! = Null &&! T.IsPrimitive && (t.GetConstructors (BindingFlags.Public) .Length! = 0 || t.GetConstructor (Type.EmptyTypes)! = Null)) e sembra lavorare! Riesci a vedere qualcosa che manca qui? Proverò a provarlo un po 'e cercherò di farti sapere se qualcosa viene fuori. –

4

Probabilmente c'è una migliore perché, ma questo funziona:

Inizialmente ho provato a utilizzare fixture.Register(() => moq.Object); anziché fixture.Customize ma registra la creatività tor funzione con OmitAutoProperties() quindi non avrebbe funzionato per voi caso.

2

A partire da 3.20.0, è possibile utilizzare AutoConfiguredMoqCustomization. Questo configurerà automaticamente tutti i mock in modo che i valori di ritorno dei loro membri siano generati da AutoFixture.

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

var mock = fixture.Create<Mock<MyObject>>(); 

Assert.NotNull(mock.Object.A); 
Assert.NotNull(mock.Object.B); 
Assert.NotNull(mock.Object.C);