2016-06-09 22 views
13

Ho un'applicazione principale asp.net che utilizza iniezione di dipendenza definito nella classe startup.cs dell'applicazione:come unità di applicazione provino asp.net con iniezione costruttore dipendenza

public void ConfigureServices(IServiceCollection services) 
    { 

     services.AddDbContext<ApplicationDbContext>(options => 
      options.UseSqlServer(Configuration["Data:FotballConnection:DefaultConnection"])); 


     // Repositories 
     services.AddScoped<IUserRepository, UserRepository>(); 
     services.AddScoped<IUserRoleRepository, UserRoleRepository>(); 
     services.AddScoped<IRoleRepository, RoleRepository>(); 
     services.AddScoped<ILoggingRepository, LoggingRepository>(); 

     // Services 
     services.AddScoped<IMembershipService, MembershipService>(); 
     services.AddScoped<IEncryptionService, EncryptionService>(); 

     // new repos 
     services.AddScoped<IMatchService, MatchService>(); 
     services.AddScoped<IMatchRepository, MatchRepository>(); 
     services.AddScoped<IMatchBetRepository, MatchBetRepository>(); 
     services.AddScoped<ITeamRepository, TeamRepository>(); 

     services.AddScoped<IFootballAPI, FootballAPIService>(); 

Questo permette simile this:

[Route("api/[controller]")] 
public class MatchController : AuthorizedController 
{ 
    private readonly IMatchService _matchService; 
    private readonly IMatchRepository _matchRepository; 
    private readonly IMatchBetRepository _matchBetRepository; 
    private readonly IUserRepository _userRepository; 
    private readonly ILoggingRepository _loggingRepository; 

    public MatchController(IMatchService matchService, IMatchRepository matchRepository, IMatchBetRepository matchBetRepository, ILoggingRepository loggingRepository, IUserRepository userRepository) 
    { 
     _matchService = matchService; 
     _matchRepository = matchRepository; 
     _matchBetRepository = matchBetRepository; 
     _userRepository = userRepository; 
     _loggingRepository = loggingRepository; 
    } 

Questo è molto pulito. Ma diventa un problema quando voglio un test unitario. Perché la mia libreria di test non ha un startup.cs in cui ho impostato l'iniezione delle dipendenze. Quindi una classe con queste interfacce come parametri sarà null.

namespace TestLibrary 
{ 
    public class FootballAPIService 
    { 
     private readonly IMatchRepository _matchRepository; 
     private readonly ITeamRepository _teamRepository; 

     public FootballAPIService(IMatchRepository matchRepository, ITeamRepository teamRepository) 

     { 
      _matchRepository = matchRepository; 
      _teamRepository = teamRepository; 

Nel codice precedente, nella libreria di test, _matchRepository e _teamRepository, sarà solo nullo. :(

Posso fare qualcosa di simile ConfigureServices, dove io definisco l'iniezione di dipendenza nel mio progetto libreria di test?

+2

Come parte del test è necessario impostare le dipendenze per il sistema in prova (SUT). Solitamente lo fai creando delle finte dipendenze prima di creare il SUT. Ma per creare il SUT è sufficiente chiamare 'new SUT (mockDependency);' va bene per il test. Pacchetto –

risposta

11

I suoi controllori a nucleo .net hanno Dependency Injection in mente fin dall'inizio, ma questo non significa che sia necessario usare un danno dipendente contenitore

Data una classe semplice come:

public class MyController : Controller 
{ 

    private readonly IMyInterface _myInterface; 

    public MyController(IMyInterface myInterface) 
    { 
     _myInterface = myInterface; 
    } 

    public JsonResult Get() 
    { 
     return Json(_myInterface.Get()); 
    } 
} 

public interface IMyInterface 
{ 
    IEnumerable<MyObject> Get(); 
} 

public class MyClass : IMyInterface 
{ 
    public IEnumerable<MyObject> Get() 
    { 
     // implementation 
    } 
} 

Così nella vostra applicazione, si sta utilizzando il contenitore iniezione di dipendenza nella vostra startup.cs, che non fa altro che fornire una concrezione di MyClass da utilizzare quando si incontra IMyInterface . Ciò non significa che sia l'unico modo per ottenere istanze di MyController tuttavia.

In un unità scenario test, si può (e si deve) fornire il proprio implementazione (o finto/stub/falso) di IMyInterface come così:

public class MyTestClass : IMyInterface 
{ 
    public IEnumerable<MyObject> Get() 
    { 
     List<MyObject> list = new List<MyObject>(); 
     // populate list 
     return list; 
    }   
} 

e nel test:

[TestClass] 
public class MyControllerTests 
{ 

    MyController _systemUnderTest; 
    IMyInterface _myInterface; 

    [TestInitialize] 
    public void Setup() 
    { 
     _myInterface = new MyTestClass(); 
     _systemUnderTest = new MyController(_myInterface); 
    } 

} 

Così, per l'ambito di unità di prova MyController, l'effettiva attuazione del IMyInterface non ha importanza (e non dovrebbe importare), solo l'interfaccia stessa conta. Abbiamo fornito un'implementazione "falsa" di IMyInterface tramite MyTestClass, ma è anche possibile farlo con un finto come tramite Moq o RhinoMocks.

In conclusione, non è effettivamente necessario il contenitore di iniezione delle dipendenze per eseguire i test, solo un'implementazione separata/controllabile/falsa/stub/falsa delle dipendenze delle classi testate.

+1

Risposta perfetta. Andrei anche fino a non usare mai un contenitore DI nel tuo test di unità. Ad eccezione dei test unitari che mirano a verificare la correttezza della configurazione DI, come l'ordine dei decoratori applicati, ad esempio –

+1

, non sono sicuro di quanto sia utile quando si hanno classi su classi che richiedono tutti un certo numero di dipendenze da iniettare. Quello che mi piacerebbe fare è essere in grado di registrare le implementazioni predefinite (o i mock con comportamenti predefiniti) in modo che io possa istanziare quei grafici degli oggetti senza dover prima impostare 30 dipendenze, ma piuttosto riconfigurare quelli di cui ho bisogno per il test. – Sinaesthetic

0

Perché si vuole iniettare quelle di una classe di test? Si potrebbe testare il solito MatchController, per esempio ., utilizzando uno strumento come RhinoMocks per creare stub o deride Ecco un esempio utilizzando tale e MSTest, da cui è possibile estrapolare:

[TestClass] 
public class MatchControllerTests 
{ 
    private readonly MatchController _sut; 
    private readonly IMatchService _matchService; 

    public MatchControllerTests() 
    { 
     _matchService = MockRepository.GenerateMock<IMatchService>(); 
     _sut = new ProductController(_matchService); 
    } 

    [TestMethod] 
    public void DoSomething_WithCertainParameters_ShouldDoSomething() 
    { 
     _matchService 
       .Expect(x => x.GetMatches(Arg<string>.Is.Anything)) 
       .Return(new []{new Match()}); 

     _sut.DoSomething(); 

     _matchService.AssertWasCalled(x => x.GetMatches(Arg<string>.Is.Anything); 
    } 
+0

RhinoMocks 3.6.1 non è compatibile con netcoreapp1.0 (.NETCoreApp, Version = v1.0). Pacchetto RhinoMocks 3.6.1 supporta: net (.NETFramework, Version = v0.0) – ganjan

+0

[Other frameworks] (http://stackoverflow.com/a/34933925/1791065) stanno lentamente prendendo questo set. –

13

È possibile controllare MyTested.AspNetCore.Mvc, che aiuta con il problema di iniezione del costruttore.

Si crea TestStartup class nel progetto di test, che eredita dalla originale:

using MyTested.AspNetCore.Mvc; 

using Microsoft.Extensions.DependencyInjection; 

public class TestStartup : Startup 
{ 
    public void ConfigureTestServices(IServiceCollection services) 
    { 
     base.ConfigureServices(services); 

     services.Replace<IService, IMockedService>(); 
    } 
} 

Si noti che non è necessario per non cambiare la configurazione EntityFramework SQL Server - la libreria sostituirà che con un ambito in memoria database, che verrà ripristinato dopo ogni test.

Allora la tua unit test è semplice, la biblioteca sarà iniettare i servizi nel controller:

[Fact] 
public void ReturnViewWhenCallingIndexAction() 
{ 
    MyMvc 
     .Controller<MatchController>() 
     .WithDbContext(db => db.WithEntities(entities => entities 
      .AddRange(SampleDataProvider.GetMatches()))) 
     .Calling(c => c.Get(1)) 
     .ShouldReturn() 
     .Ok() 
     .WithResponseModelOfType<ResponseModel>() 
     .Passing(m => 
     { 
      Assert.AreEqual(1, m.Id); 
      Assert.AreEqual("Some property value", m.SomeProperty); 
     }); 
} 

Per ulteriori informazioni, controllare il samples e web-site.

+0

In quale versione services.Replace disponibile? –

1

Sebbene @ risposta di Kritner è corretta, io preferisco il seguente codice per l'integrità e la migliore esperienza DI: