6

Sto iniziando a entrare in Unit Testing, Dependancy Injection e tutto ciò che jazz mentre costruisco il mio ultimo progetto ASP.NET MVC.In che modo è possibile testare i controller dell'unità senza un contenitore IoC?

Sono al punto in cui ora vorrei testare i miei controller unitari e ho difficoltà a capire come farlo in modo appropriato senza un contenitore IoC.

Prendiamo ad esempio un semplice controllo:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository = new SqlQuestionsRepository(); 

    // ... Continue with various controller actions 
} 

Questa classe non è molto unità verificabile a causa della sua istanziazione diretta di SqlQuestionsRepository. Quindi, consente di andare giù per il percorso di iniezione delle dipendenze e fare:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository; 

    public QuestionsController(IQuestionsRepository repository) 
    { 
     _repository = repository; 
    } 
} 

Questo sembra migliore. Ora posso facilmente scrivere test unitari con un finto IQuestionsRepository. Tuttavia, che cosa sta per istanziare il controller ora? Da qualche parte più avanti la catena di chiamate SqlQuestionRepository dovrà essere istanziata. Sembra che abbia semplicemente spostato il problema altrove, senza esserne liberato.

Ora, so che questo è un buon esempio di dove un contenitore IoC può essere d'aiuto collegando per me le dipendenze dei Controllori e allo stesso tempo mantenendo il controller facilmente controllabile.

La mia domanda è, come si suppone di fare test unitari su cose di questo tipo senza un contenitore IoC?

Nota: non sono contrario ai contenitori IoC e probabilmente andrò presto su quella strada. Tuttavia, sono curioso di sapere quale sia l'alternativa per le persone che non li usano.

risposta

2

Non è possibile mantenere l'istanziazione diretta del campo e fornire anche il setter? In questo caso chiameresti il ​​setter solo durante i test unitari. Qualcosa di simile a questo:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository = new SqlQuestionsRepository(); 

    // Really only called during unit testing... 
    public QuestionsController(IQuestionsRepository repository) 
    { 
     _repository = repository; 
    } 
} 

io non sono troppo familiarità con .NET, ma come una nota a margine in Java questo è un modo comune di refactoring del codice esistente per migliorare la verificabilità. I.E., se hai classi già in uso e devi modificarle in modo da migliorare la copertura del codice senza rompere le funzionalità esistenti.

Il nostro team ha già fatto questo, e di solito impostiamo la visibilità del setter su package-private e manteniamo il pacchetto della classe di test lo stesso in modo che possa chiamare il setter.

+0

@Peter, ottimo suggerimento. Hai mai avuto esperienza con l'utilizzo di contenitori IoC sui tuoi progetti o non ne hai ancora trovato la necessità? – mmcdole

+0

Non ho molta esperienza .NET, la maggior parte del mio lavoro è con Java usando Spring per IoC. È stato molto utile per migliorare la modularità. – Peter

+0

@Peter: questo è praticamente lo schema che utilizzo. Ho postato una risposta che fa la stessa cosa, ma usa alcune caratteristiche del linguaggio C# che tu, essendo un ragazzo Java, potresti non essere a conoscenza. –

2

Si potrebbe avere un costruttore predefinito con il controller che avrà una sorta di comportamento predefinito.

Qualcosa di simile ...

public QuestionsController() 
    : this(new QuestionsRepository()) 
{ 
} 

In questo modo per impostazione predefinita quando la fabbrica di controllo è la creazione di una nuova istanza del controller userà il comportamento del costruttore di default. Poi nei tuoi test unitari potresti usare una struttura di derisione per passare una simulazione nell'altro costruttore.

1

Una delle opzioni consiste nell'utilizzare i falsi.

public class FakeQuestionsRepository : IQuestionsRepository { 
    public FakeQuestionsRepository() { } //simple constructor 
    //implement the interface, without going to the database 
} 

[TestFixture] public class QuestionsControllerTest { 
    [Test] public void should_be_able_to_instantiate_the_controller() { 
     //setup the scenario 
     var repository = new FakeQuestionsRepository(); 
     var controller = new QuestionsController(repository); 
     //assert some things on the controller 
    } 
} 

Un'altra possibilità è quella di utilizzare prende in giro e di un quadro di scherno, che può generare automaticamente questi deride al volo.

[TestFixture] public class QuestionsControllerTest { 
    [Test] public void should_be_able_to_instantiate_the_controller() { 
     //setup the scenario 
     var repositoryMock = new Moq.Mock<IQuestionsRepository>(); 
     repositoryMock 
      .SetupGet(o => o.FirstQuestion) 
      .Returns(new Question { X = 10 }); 
     //repositoryMock.Object is of type IQuestionsRepository: 
     var controller = new QuestionsController(repositoryMock.Object); 
     //assert some things on the controller 
    } 
} 

Riguardo a dove vengono costruiti tutti gli oggetti. In un test unitario, si imposta solo un insieme minimo di oggetti: un oggetto reale che è sotto test e alcune dipendenze simulate o simulate che l'oggetto reale sotto test richiede. Ad esempio, l'oggetto reale sotto test è un'istanza di QuestionsController - ha una dipendenza su IQuestionsRepository, quindi gli forniamo o un falso IQuestionsRepository come nel primo esempio o un simulato IQuestionsRepository come nel secondo esempio.

Nel sistema reale, tuttavia, si imposta l'intero contenitore al livello più alto del software. Ad esempio, in un'applicazione Web, si imposta il contenitore, si collegano tutte le interfacce e le classi di implementazione, in GlobalApplication.Application_Start.

0

Sto espandendo un po 'la risposta di Peter.

Nelle applicazioni con molti tipi di entità, non è raro che un controller richieda riferimenti a più repository, servizi, qualunque sia. Trovo noioso passare manualmente tutte quelle dipendenze nel mio codice di test (soprattutto dal momento che un determinato test può coinvolgere solo uno o due di essi). In questi scenari, preferisco lo stile IOC dell'iniezione setter rispetto all'iniezione del costruttore. Il modello Io lo uso questo:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository Repository 
    { 
     get { return _repo ?? (_repo = IoC.GetInstance<IQuestionsRepository>()); } 
     set { _repo = value; } 
    } 
    private IQuestionsRepository _repo; 

    // Don't need anything fancy in the ctor 
    public QuestionsController() 
    { 
    } 
} 

Sostituire IoC.GetInstance<> con qualunque sintassi usa i tuoi particolare framework IOC.

In produzione, nulla invocherà il setter della proprietà, quindi la prima volta che viene chiamato il getter il controller chiamerà il framework IOC, riceverà un'istanza e la memorizzerà.

Nella prova, è sufficiente chiamare il setter prima di invocare i metodi del controller:

var controller = new QuestionsController { 
    Repository = MakeANewMockHoweverYouNormallyDo(...); 
} 

I vantaggi di questo approccio, secondo me:

  1. ancora prende vantaggio di CIO in produzione.
  2. Più facile per costruire manualmente i controller durante il test. Hai solo bisogno di inizializzare le dipendenze che il test utilizzerà effettivamente.
  3. È possibile creare configurazioni IOC specifiche del test, se non si desidera configurare manualmente le dipendenze comuni.