Ich habe eine asp.net-Kernanwendung, die die Abhängigkeitsinjektion verwendet, die in der Startup.cs-Klasse der Anwendung definiert ist:
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>();
Dies ermöglicht so etwas:
[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;
}
Das ist sehr ordentlich. Wird aber zum Problem, wenn ich Unit-Test machen möchte. Weil meine Testbibliothek keine startup.cs hat, in der ich die Abhängigkeitsinjektion einrichte. Eine Klasse mit diesen Schnittstellen als Parameter ist also einfach null.
namespace TestLibrary
{
public class FootballAPIService
{
private readonly IMatchRepository _matchRepository;
private readonly ITeamRepository _teamRepository;
public FootballAPIService(IMatchRepository matchRepository, ITeamRepository teamRepository)
{
_matchRepository = matchRepository;
_teamRepository = teamRepository;
Im obigen Code sind in der Testbibliothek _matchRepository und _teamRepository nur null . :(
Kann ich so etwas wie ConfigureServices ausführen, bei dem ich die Abhängigkeitsinjektion in meinem Testbibliotheksprojekt definiere?
new SUT(mockDependency);
ist jedoch für Ihren Test in Ordnung.Antworten:
Ihre Controller im .net-Kern berücksichtigen von Anfang an die Abhängigkeitsinjektion. Dies bedeutet jedoch nicht, dass Sie einen Abhängigkeitsinjektionscontainer verwenden müssen.
Bei einer einfacheren Klasse wie:
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 } }
In Ihrer App verwenden Sie also den Abhängigkeitsinjektionscontainer in Ihrer App, der lediglich
startup.cs
eine Konkretion fürMyClass
die Verwendung bereitstellt , wenn erIMyInterface
auftritt. Dies bedeutet jedoch nicht, dass dies der einzige Weg ist, um Instanzen von zu erhaltenMyController
.In einem Unit- Test-Szenario können (und sollten) Sie Ihre eigene Implementierung (oder Mock / Stub / Fake)
IMyInterface
wie folgt bereitstellen :public class MyTestClass : IMyInterface { public IEnumerable<MyObject> Get() { List<MyObject> list = new List<MyObject>(); // populate list return list; } }
und in deinem Test:
[TestClass] public class MyControllerTests { MyController _systemUnderTest; IMyInterface _myInterface; [TestInitialize] public void Setup() { _myInterface = new MyTestClass(); _systemUnderTest = new MyController(_myInterface); } }
Für den Umfang der Unit-Tests
MyController
spielt die tatsächliche Implementierung vonIMyInterface
keine Rolle (und sollte auch keine Rolle spielen), sondern nur die Schnittstelle selbst. Wir haben eine "gefälschte" Implementierung vonIMyInterface
through bereitgestelltMyTestClass
, aber Sie können dies auch mit einem Mock wie throughMoq
oder tunRhinoMocks
.Unterm Strich benötigen Sie nicht den Abhängigkeitsinjektionscontainer, um Ihre Tests durchzuführen, sondern nur eine separate, kontrollierbare Implementierung / Mock / Stub / Fake Ihrer getesteten Klassenabhängigkeiten.
quelle
ServiceCollection
von Anfang an genau einstellen, welche Objekte in unserem verfügbar sind . Es ist besonders hilfreich für Gerüste und auch für Integrationstests. Ja, ich würde DI in Ihren Tests verwenden.Obwohl die Antwort von @ Kritner richtig ist, bevorzuge ich Folgendes für die Codeintegrität und eine bessere DI-Erfahrung:
[TestClass] public class MatchRepositoryTests { private readonly IMatchRepository matchRepository; public MatchRepositoryTests() { var services = new ServiceCollection(); services.AddTransient<IMatchRepository, MatchRepositoryStub>(); var serviceProvider = services.BuildServiceProvider(); matchRepository = serviceProvider.GetService<IMatchRepository>(); } }
quelle
GetService<>
hat einige Überladungen, die mit gefunden werden könnenusing Microsoft.Extensions.DependencyInjection
Auf einfache Weise habe ich eine generische Hilfsklasse für Abhängigkeitsauflöser geschrieben und dann den IWebHost in meiner Unit-Test-Klasse erstellt.
Generic Dependency Resolver
public class DependencyResolverHelpercs { private readonly IWebHost _webHost; /// <inheritdoc /> public DependencyResolverHelpercs(IWebHost WebHost) => _webHost = WebHost; public T GetService<T>() { using (var serviceScope = _webHost.Services.CreateScope()) { var services = serviceScope.ServiceProvider; try { var scopedService = services.GetRequiredService<T>(); return scopedService; } catch (Exception e) { Console.WriteLine(e); throw; } }; } } }
Unit Test Projekt
[TestFixture] public class DependencyResolverTests { private DependencyResolverHelpercs _serviceProvider; public DependencyResolverTests() { var webHost = WebHost.CreateDefaultBuilder() .UseStartup<Startup>() .Build(); _serviceProvider = new DependencyResolverHelpercs(webHost); } [Test] public void Service_Should_Get_Resolved() { //Act var YourService = _serviceProvider.GetService<IYourService>(); //Assert Assert.IsNotNull(YourService); } }
quelle
Once Dispose is called, any scoped services that have been resolved from ServiceProvider will be disposed.
.Wenn Sie die
Program.cs
+Startup.cs
-Konvention verwenden und diese schnell zum Laufen bringen möchten, können Sie Ihren vorhandenen Host-Builder mit einem Einzeiler wiederverwenden:using MyWebProjectNamespace; public class MyTests { readonly IServiceProvider _services = Program.CreateHostBuilder(new string[] { }).Build().Services; // one liner [Test] public void GetMyTest() { var myService = _services.GetRequiredService<IMyService>(); Assert.IsNotNull(myService); } }
Beispieldatei
Program.cs
aus einem Webprojekt:using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; namespace MyWebProjectNamespace { public class Program { public static void Main(string[] args) => CreateHostBuilder(args).Build().Run(); public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); } }
quelle
Warum sollten Sie diese in eine Testklasse injizieren wollen? Normalerweise testen Sie den MatchController beispielsweise mit einem Tool wie RhinoMocks , um Stubs oder Mocks zu erstellen. Hier ist ein Beispiel mit diesem und MSTest, aus dem Sie extrapolieren können:
[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); }
quelle