6

Ich fange an, Unit-Testing, Dependancy Injection und all diesen Jazz zu lernen, während ich mein neuestes ASP.NET MVC-Projekt erstelle.Wie können Sie Unit Controller ohne IoC-Container testen?

Ich bin jetzt an dem Punkt, wo ich Unit Controller meine Controller testen möchte und ich habe Schwierigkeiten herauszufinden, wie dies ohne einen IoC-Container geeignet tun.

Nehmen Sie zum Beispiel eine einfache Steuerung:

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

    // ... Continue with various controller actions 
} 

Diese Klasse ist nicht sehr Einheit überprüfbare wegen seiner direkten Instanziierung SqlQuestionsRepository. Also, lassen Sie die Dependancy Injection Route gehen und tun Sie:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository; 

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

Dies scheint besser. Ich kann jetzt Unit-Tests mit einem Pseudo-IQuestionsRepository schreiben. Was wird den Controller jetzt instanziieren? Irgendwo weiter oben muss die Aufrufkette SqlQuestionRepository instanziiert werden. Es scheint, als ob ich das Problem einfach anderswo verschoben, nicht losgeworden bin.

Jetzt weiß ich, dass dies ein gutes Beispiel dafür ist, wo ein IoC-Container Ihnen helfen kann, indem er die Controller-Abhängigkeiten für mich verdrahtet und gleichzeitig meinen Controller leicht testbar hält.

Meine Frage ist, wie soll man Unit-Tests auf Dinge dieser Art tun ohne ein IoC-Container?

Hinweis: Ich bin nicht gegen IoC-Container, und ich werde wahrscheinlich bald diese Straße gehen. Aber ich bin gespannt, was die Alternative für Leute ist, die sie nicht benutzen.

Antwort

2

Ist es nicht möglich, die direkte Instanziierung des Feldes zu behalten und auch den Setter bereitzustellen? In diesem Fall würden Sie den Setter nur während des Komponententests aufrufen. Etwas wie folgt aus:

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

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

Ich bin nicht allzu vertraut mit .NET, sondern als eine Randnotiz in Java dies ist ein gemeinsamer Weg vorhandenen Code Refactoring die Testbarkeit zu verbessern. I. E., wenn Sie Klassen haben, die bereits verwendet werden und diese modifizieren müssen, um die Codeabdeckung zu verbessern, ohne bestehende Funktionalität zu unterbrechen.

Unser Team hat dies schon einmal gemacht, und normalerweise setzen wir die Sichtbarkeit des Setter auf package-private und halten das Paket der Testklasse gleich, damit es den Setter aufrufen kann.

+0

@Peter, sehr guter Vorschlag. Hatten Sie Erfahrungen mit der Verwendung von IoC-Containern in Ihren Projekten oder haben Sie noch keinen Bedarf gefunden? – mmcdole

+0

Ich habe nicht viel .NET Erfahrung, die meisten meiner Arbeit ist mit Java mit Spring für IoC. Es war sehr nützlich für die Verbesserung der Modularität. – Peter

+0

@Peter: Das ist so ziemlich das Muster, das ich verwende. Ich habe eine Antwort gepostet, die dasselbe tut, aber einige C# -Sprachenfunktionen verwendet, die Sie als Java-Typ vielleicht nicht kennen. –

2

Sie könnten einen Standardkonstruktor mit Ihrem Controller haben, der eine Art Standardverhalten hat.

So etwas wie ...

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

Auf diese Weise standardmäßig, wenn der Controller Factory ist eine neue Instanz des Controllers zu schaffen wird das Standard-Konstruktor Verhalten verwenden. Dann könnten Sie in Ihren Unit-Tests ein spöttisches Framework verwenden, um einen Mock in den anderen Konstruktor zu übertragen.

1

Eine Möglichkeit besteht darin, Fälschungen zu verwenden.

Eine weitere Option ist die Verwendung von Mocks und eines Mocking-Frameworks, mit dem diese Mocks automatisch generiert werden können.

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

In Bezug darauf, wo alle Objekte aufgebaut werden. In einem Komponententest richten Sie nur eine minimale Menge von Objekten ein: ein reales Objekt, das sich im Test befindet, und einige gefälschte oder verspottete Abhängigkeiten, die das zu testende reale Objekt erfordert. Zum Beispiel ist das reale Objekt im Test eine Instanz von QuestionsController - es hat eine Abhängigkeit von IQuestionsRepository, so geben wir es entweder eine falsche wie im ersten Beispiel oder eine Schein IQuestionsRepository wie im zweiten Beispiel.

Im realen System richten Sie jedoch den gesamten Container auf der obersten Ebene der Software ein. Beispielsweise richten Sie in einer Webanwendung den Container ein und verdrahten alle Schnittstellen und die implementierenden Klassen in GlobalApplication.Application_Start.

0

Ich erweitere Peters Antwort ein wenig.

In Anwendungen mit vielen Entitätstypen ist es nicht ungewöhnlich, dass ein Controller Verweise auf mehrere Repositorys, Dienste oder was auch immer benötigt. Ich finde es mühsam, all diese Abhängigkeiten in meinem Testcode manuell zu übergeben (besonders, da ein bestimmter Test nur einen oder zwei von ihnen beinhalten kann). In diesen Szenarien bevorzuge ich Setter-Injektion Stil IOC über Konstruktor Injektion. Das Muster, das ich es verwende dieses:

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() 
    { 
    } 
} 

ersetzen IoC.GetInstance<> mit dem, was Syntax Ihres spezielles IOC-Framework verwendet.

In der Produktionsanwendung wird nichts den Property Setter aufrufen. Wenn der Getter das erste Mal aufgerufen wird, ruft der Controller das IOC Framework auf, ruft eine Instanz ab und speichert sie.

In Test, müssen Sie kurz vor dem Setter rufen alle Controller-Methoden zum Aufruf:

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

Die Vorteile dieses Ansatzes, IMHO:

  1. Noch Vorteil von IOC nimmt in der Produktion.
  2. Leichteres manuelle Erstellen Ihrer Controller während des Tests. Sie müssen nur die Abhängigkeiten initialisieren, die Ihr Test tatsächlich verwendet.
  3. Sie können testspezifische IOC-Konfigurationen erstellen, wenn Sie allgemeine Abhängigkeiten nicht manuell konfigurieren möchten.
Verwandte Themen