web-dev-qa-db-fra.com

comment tester à l'unité l'application centrale asp.net avec l'injection de dépendance de constructeur

J'ai une application principale asp.net qui utilise l'injection de dépendance définie dans la classe startup.cs de l'application:

    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>();

Cela permet quelque chose comme ça:

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

C'est très chouette. Mais devient un problème lorsque je veux effectuer des tests unitaires. Parce que ma bibliothèque de tests n'a pas de startup.cs où je configure l'injection de dépendance. Donc une classe avec ces interfaces comme params sera juste nulle.

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

        public FootballAPIService(IMatchRepository matchRepository, ITeamRepository teamRepository)

        {
            _matchRepository = matchRepository;
            _teamRepository = teamRepository;

Dans le code ci-dessus, dans la bibliothèque de tests, _ matchRepository et _ teamRepository, sera simplement null. :(

Puis-je faire quelque chose comme ConfigureServices, où je définis l'injection de dépendance dans mon projet de bibliothèque de test?

42
ganjan

Vos contrôleurs dans .net core ont à l’esprit une injection de dépendance dès le début, mais cela ne signifie pas que vous devez utiliser un conteneur d’injection de dépendance.

Étant donné une classe plus simple comme:

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

Ainsi, dans votre application, vous utilisez le conteneur d'injection de dépendance dans votre startup.cs, qui ne fait que fournir une concrétion de MyClass à utiliser lorsque IMyInterface est rencontré. Cela ne signifie toutefois pas que c'est le seul moyen d'obtenir des instances de MyController.

Dans un scénario de test , vous pouvez (et devriez) fournir votre propre implémentation (ou maquette/module/bloc) de IMyInterface ainsi:

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

et dans votre test:

[TestClass]
public class MyControllerTests
{

    MyController _systemUnderTest;
    IMyInterface _myInterface;

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

}

Donc, pour la portée des tests unitaires MyController, la mise en oeuvre réelle de IMyInterface n’a aucune importance (et ne devrait pas être matière), seule l’interface elle-même compte. Nous avons fourni une "fausse" implémentation de IMyInterface à MyTestClass, mais vous pouvez également le faire avec une simulation comme par le biais de Moq ou RhinoMocks.

En bout de ligne, vous n’avez pas réellement besoin du conteneur d’injection de dépendance pour effectuer vos tests, mais seulement d’une dépendance séparée, contrôlable, implementation/mock/stub/fake de vos dépendances de classes testées.

21
Kritner

Bien que la réponse de @ Kritner soit correcte, je préfère ce qui suit pour l'intégrité du code et une meilleure expérience DI:

[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

De manière simple, j’ai écrit une classe d’assistance générique pour le résolveur de dépendances, puis créé l’IWebHost dans ma classe de test unitaire.

résolveur de dépendance générique

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

Projet de test unitaire

  [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

Pourquoi voudriez-vous les injecter dans une classe de test? Vous testerez généralement MatchController, par exemple, en utilisant un outil tel que RhinoMocks pour créer des stubs ou des mocks. Voici un exemple utilisant cela et MSTest, à partir duquel vous pouvez extrapoler:

[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