2015-11-13 25 views
5

Considerate le seguenti classi:Iniettare Sia interfaccia e implementazione in AutoFixture

public interface IInterface {} 
public class Class : IInterface {} 

public class Customization : ICustomization 
{ 
    readonly IInterface item; 

    public Customization() : this(new Class()) {} 

    public Customization(IInterface item) 
    { 
     this.item = item; 
    } 

    public void Customize(IFixture fixture) 
    { 
     fixture.Inject(item); 
     var created = fixture.Create<Class>(); // Would like this to resolve as item from previous line. 
    } 
} 

Il problema che sto funzionando in è che la IInterface viene iniettata, mentre la Class non è. C'è un modo per iniettare sia IInterface e Class in modo che venga restituita la stessa istanza per entrambi?

Si prega di notare che mi piacerebbe farlo utilizzando un ICustomization (o all'interno di un ICustomization) e non con gli attributi su un metodo di prova. Sto cercando di fare un'iniezione personalizzata su queste due classi. Se utilizzo il parametro [Frozen(Matching.ImplementedInterfaces)]Class item come parametro, non funziona, poiché la Classe che è bloccata sovrascrive il valore iniettato nel metodo ICustomization.Customize.

Si prega di notare inoltre che questo è un codice di esempio e non come lo sto usando. Nel metodo di prova xUnit, vorrei l'istanza Class specificato come parametro di essere congelati IInstance sopra:

public void MyTest(IInterface @interface, Class implementation) 
{ 
    Assert.Same(@interface, implementation); 
} 
+1

Quel sovraccarico di 'Freeze' non fa quello che pensi che faccia; vedere [la documentazione] (https://github.com/AutoFixture/AutoFixture/blob/master/Src/AutoFixture/FixtureFreezer.cs#L43-L72). Vedi la risposta di Enrico Campidoglio per un modo abbastanza idiomatico di ottenere il risultato desiderato. Un'altra opzione sarebbe quella di utilizzare una delle estensioni del contenitore auto-mocking AutoFixture, che fondamentalmente hanno funzionalità integrate di questo tipo. –

+0

Mi scuso @MarkSeemann, sono fallito. Ho visto l'altra discussione su Inject/Freeze e mi sono confuso. Volevo dire Inject e non Freeze, e ho aggiornato la domanda di conseguenza. –

risposta

0

OK, questo ha richiesto un'eternità per capire, ma questa domanda/scenario è in definitiva dovuta al cattivo design da parte mia e all'inesperienza e all'apprendimento di AutoFixture. Lo scenario reale di ciò che stavo cercando di fare era registrare un container IoC con il mio dispositivo, e volevo che sia l'IServiceLocator che la sua implementazione fossero registrati con il Fixture in modo che fossero disponibili per l'iniezione di valori nel metodo di test corrente.

Questo problema è stato risolto aggiungendo a Customization per inoltrare le richieste a un IServiceLocator fornito (indicato anche here in this question).

Inoltre, ho fatto an extension method che fa uso di Dynamitey che fa quello che stavo cercando, ma non è più utilizzato e probabilmente lo eliminerò ad un certo punto.

Quindi la risposta è: se si desidera eseguire questa operazione per qualche motivo, si è più che probabile doing it wrong. Sono a metà strada tentato di cancellare questa risposta, ma la lascerò qui nel caso in cui un altro newb come me incontra lo stesso problema e potrebbe trarre beneficio da questo.

Infine, vorrei ringraziare @ enrico-campidoglio e @ mark-seemann per la loro pazienza e le risposte/assistenza di qualità davvero informativa/di qualità (e anche per nessuno che sottovaluta questa domanda). So che la barra è impostata qui in alto @ Stack Overflow e probabilmente avrei dovuto avere un po 'più di pazienza prima di pubblicare questa domanda.

2

Certo, è possibile applicare l'attributo [Frozen] sul parametro classe concreta e specificare ImplementedInterfaces come corrispondenza criteri:

[Theory, AutoData] 
public void Test(
    [Frozen(Matching.ImplementedInterfaces)]Class implementation, 
    IInterface @interface) 
{ 
    Assert.Same(implementation, @interface); 
} 

che racconta AutoFixture per fornire la stessa Class esempio ogni volta che deve creare un valore per qualsiasi della sua attuazione interfacce ed.

+0

Grazie per la risposta, @ enrico-campidoglio. Mi scuso per non essere più chiaro, sto cercando di iniettare e non congelare. Inoltre, sto cercando di iniettare all'interno della personalizzazione IC e non a livello di attributo. Ho aggiornato la domanda per essere più concisa/accurata. Per favore, scusa la mia nuova fortuna! –

4

Se è assolutamente necessario farlo da soli

Se si guarda più da vicino il metodo Inject, si noterà che in realtà è un metodo generico ma che l'argomento tipo è dedotto quando lo si utilizza come lo si utilizza . Se vuoi bloccare entrambi, puoi - devi semplicemente invocare Inject per ogni tipo.

Ecco un Customization leggermente modificato. Al fine di prevenire un abbattuto eventualmente nullo, ho cambiato in modo che la sua item campo (e il corrispondente argomento item costruttore) è del tipo Class:

public class Customization : ICustomization 
{ 
    readonly Class item; 

    public Customization() : this(new Class()) { } 

    public Customization(Class item) 
    { 
     this.item = item; 
    } 

    public void Customize(IFixture fixture) 
    { 
     fixture.Inject(item); 
     fixture.Inject<IInterface>(item); 
    } 
} 

noti che Customize inietta lo stesso elemento due volte. Nella prima riga, l'argomento di tipo generico viene dedotto da Class dal compilatore, mentre nella seconda riga l'argomento di tipo IInterface viene definito in modo esplicito.

Data questa implementazione, il seguente test viene superato:

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

    var c = fixture.Create<Class>(); 
    var i = fixture.Create<IInterface>(); 

    Assert.Equal(c, i); 
} 

Utilizzando il built-in API

Detto questo, mi piacerebbe prendere in considerazione più facile usare semplicemente il built-in API:

[Fact] 
public void UseTypeRelay() 
{ 
    var fixture = new Fixture(); 
    fixture.Customizations.Add(
     new TypeRelay(
      typeof(IInterface), 
      typeof(Class))); 
    fixture.Freeze<Class>(); 

    var c = fixture.Create<Class>(); 
    var i = fixture.Create<IInterface>(); 

    Assert.Equal(c, i); 
} 

TypeRelay mappe IInterface-Class, il che significa che tutte le richieste di IInterface saranno inoltrati alle richieste f oppure Class.Quando Class è bloccato, significa che non solo è Class bloccato, ma lo è anche IInterface.