it-swarm.com.de

Wie teste ich die asp.net-Kernanwendung mit Konstruktorabhängigkeitsinjektion?

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 will. Weil meine Testbibliothek kein startup.cs hat, in dem 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 ist 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?

42
ganjan

Ihre Controller in .net core haben von Anfang an die Abhängigkeitsinjektion im Auge, 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 den Container für die Abhängigkeitsinjektion in Ihrem startup.cs, der nur eine Konkretion von MyClass liefert, die verwendet wird, wenn IMyInterface auftritt. Dies bedeutet jedoch nicht, dass dies die einzige Möglichkeit ist, Instanzen von MyController abzurufen.

In einem unit Testszenario können (und sollten) Sie Ihre eigene Implementierung (oder Mock/Stub/Fake) von 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 des Unit-Tests MyController spielt die tatsächliche Implementierung von IMyInterface also keine Rolle (und sollte nicht), Nur die Schnittstelle selbst ist von Bedeutung. Wir haben eine "gefälschte" Implementierung von IMyInterface bis MyTestClass bereitgestellt, aber Sie können dies auch mit einem Mock wie durch Moq oder RhinoMocks tun.

Unterm Strich benötigen Sie nicht den Dependency Injection Container, um Ihre Tests durchzuführen, sondern nur eine separate, kontrollierbare Implementierung/Mock/Stub/Fake Ihrer getesteten Klassenabhängigkeiten.

21
Kritner

Obwohl die Antwort von @ Kritner richtig ist, bevorzuge ich aus Gründen der Code-Integrität und einer besseren DI-Erfahrung Folgendes:

[TestClass]
public class MatchRepositoryTests
{
    private readonly IMatchRepository matchRepository;

    public MatchRepositoryTests()
    {
        var services = new ServiceCollection();
        services.AddTransient<IMatchRepository, MatchRepository>();

        var serviceProvider = services.BuildServiceProvider();

        matchRepository = serviceProvider.GetService<IMatchRepository>();
    }
}
61
madjack

Auf einfache Weise habe ich eine allgemeine Helferklasse für Abhängigkeitsauflöser geschrieben und dann den IWebHost in meine Unit-Test-Klasse eingebaut.

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

nit Test Project

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


    }
18
Joshua Duxbury

Warum sollten Sie diese in einer Testklasse injizieren? Normalerweise testen Sie den MatchController, indem Sie beispielsweise ein Tool wie RhinoMocks verwenden, um Stubs oder Mocks zu erstellen. Hier ist ein Beispiel, das das und MSTest verwendet und 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);
    }