2016-04-20 5 views
3

Sprich, ich habe die unten-ControllerWie kann ich ein Mock-Objekt übergeben, wenn ich Setter Injektion in ASP.NET MVC-Controller tue

public class UsersController : Controller 
{ 
    private IUsersRepository UsersRepository { get; } 
    public UsersController() 
    { 
     UsersRepository = DependencyResolver.Current.GetService(typeof(IUsersRepository)) as IUsersRepository; 
    } 
    public ActionResult Index() 
    { 
     MyUserDefinedModel data = UsersRepository.MyRepository(); 
     return View(data); 
    } 
} 

Jetzt möchte ich die IUsersRepository und übergeben sie an die Steuerung verspotten in mein Testskript.

Below mein Testcode

public class UsersListTest 
    { 
     private UsersController usersController = new Mock<IUsersRepository>(); 
     private Mock<IUsersRepository> usersRepository = new UsersController(); 
     [TestMethod] 
     public void TestMethod1() 
     { 
      //usersRepository.Setup(x => x.Get()).Returns(users); 
     } 
    } 

Wie weil private IUsersRepository UsersRepository { get; } privat, ich bin nicht in der Lage, die spotten IUsersRepository passieren.

Was wäre die gute Idee, Komponententest und Mock in diesem Fall zu schreiben.

+0

Warum registrieren Sie Ihr Mock-Objekt nicht im DI-Container? es könnte die einfachste Lösung sein .... –

Antwort

2

Der Grund, dass Sie Probleme mit dem Testen haben, weil Ihr Controller Klasse die Service Locator anti-pattern verwendet. Ein Service Locator ist entweder eine globale Instanz (DependencyResolver.Current) oder eine Abstraktion, die es ermöglicht, Abhängigkeiten zur Laufzeit aufzulösen. Einer der vielen Nachteile des Service Locators sind die Probleme, die beim Testen auftreten.

Sie sollten sich vom Service Locator-Muster entfernen und stattdessen die Abhängigkeitsinjektion verwenden, was eine vorteilhafte Konstruktorinjektion darstellt. Ihre Anwendungskomponenten sollten eine single public constructor haben und diese Konstruktoren sollten nichts weiter tun als storing the incoming dependencies. Dies wird in der folgenden UsersController Implementierung führen:

public class UsersController : Controller 
{ 
    private IUsersRepository usersRepository; 
    public UsersController(IUsersRepository usersRepository) 
    { 
     this.usersRepository = usersRepository; 
    } 
    public ActionResult Index() 
    { 
     return View(this.usersRepository.MyRepository()); 
    } 
} 

In diesem Ort, Testeinheit wurde trivial:

public class UsersControllerTests 
{ 
    [TestMethod] 
    public void Index_Always_CallsRepository() 
    { 
     // Arrange 
     var repository = new Mock<IUsersRepository>(); 
     var controller = CreateValidUsersController(repository.Instance); 

     // Act 
     var result = controller.Index(); 

     // Assert 
     Assert.IsTrue(repository.IsCalled); 
    } 

    // Factory method to simplify creation of the class under test with its dependencies 
    private UsersController CreateValidUsersController(params object[] deps) { 
     return new UsersController(
      deps.OfType<IUsersRepository>().SingleOrDefault() ?? Fake<IUsersRepository>() 
      // other dependencies here 
      ); 
    } 

    private static T Fake<T>() => (new Mock<T>()).Instance; 
} 

Dies ist jedoch nicht, Sie zwingen MVC-Standard IControllerFactory zu ändern, da out-of- In der Box kann MVC nur Controller mit einem Standardkonstruktor behandeln. Aber das ist trivial und sieht wie folgt aus:

public sealed class CompositionRoot : DefaultControllerFactory 
{ 
    private static string connectionString = 
     ConfigurationManager.ConnectionStrings["app"].ConnectionString; 

    protected override IController GetControllerInstance(RequestContext _, Type type) { 

     if (type == typeof(UsersController)) 
      return new UsersController(new UsersRepository()); 

     // [other controllers here] 

     return base.GetControllerInstance(_, type); 
    } 
} 

Ihre neue Controller Fabrik kann in MVC angeschlossen werden, wie folgt:

public class MvcApplication : System.Web.HttpApplication 
{ 
    protected void Application_Start() { 
     ControllerBuilder.Current.SetControllerFactory(new CompositionRoot()); 

     // the usual stuff here 
    } 
} 

Sie können here ein vollständigeres Beispiel finden.

+0

Einverstanden, aber die meisten DI-Container haben bereits ein Paket (nugget) für die Integration mit ASP.NET. Keine Notwendigkeit, die ControllerFactory Sachen selbst zu schreiben. –

+0

@HenkHolterman: Ich glaube nicht, dass das OP einen DI-Container verwendet. – Steven

+0

@Riki: Ich habe ein Unit-Test-Beispiel hinzugefügt. – Steven

1

Sie könnten einen Konstruktor hinzufügen, mit dem Sie eine Kopie von IUsersRepository bereitstellen können. Ihr Standardkonstruktors nennen würde dies mit dem Beispiel aus dem DependencyResolver, wie folgt aus:

public class UsersController : Controller 
{ 
    private IUsersRepository UsersRepository { get; } 

    public UsersController(IUsersRepository usersRepository) 
    { 
     UsersRepository = usersRepository; 
    } 

    public UsersController():this(DependencyResolver.Current.GetService(typeof(IUsersRepository)) as IUsersRepository) 
    { 

    } 

    public ActionResult Index() 
    { 
     MyUserDefinedModel data = UsersRepository.MyRepository(); 
     return View(data); 
    } 

}

Verwandte Themen