web-dev-qa-db-ja.com

コンストラクター依存性注入を使用したasp.netコアアプリケーションの単体テスト方法

アプリケーションのstartup.csクラスで定義された依存性注入を使用するasp.netコアアプリケーションがあります。

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

これにより、次のようなことが可能になります。

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

これは非常にきれいです。しかし、ユニットテストをしたいときは問題になります。私のテストライブラリには、依存性注入を設定するstartup.csがないためです。したがって、これらのインターフェイスをparamsとして持つクラスは、nullになります。

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

        public FootballAPIService(IMatchRepository matchRepository, ITeamRepository teamRepository)

        {
            _matchRepository = matchRepository;
            _teamRepository = teamRepository;

上記のコードのテストライブラリでは、_ matchRepositoryおよび_ teamRepositoryは、nullになります。 :(

テストライブラリプロジェクトで依存性注入を定義するConfigureServicesのようなことはできますか?

42
ganjan

.netコアのコントローラーは、最初から依存性注入を念頭に置いていますが、これは、依存性注入コンテナーを使用する必要があるという意味ではありません。

次のような単純なクラスを考えます:

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

したがって、アプリでは、startup.csで依存性注入コンテナを使用しています。これは、MyClassが検出されたときに使用するIMyInterfaceのコンクリーションを提供するだけです。ただし、これはMyControllerのインスタンスを取得する唯一の方法ではありません。

unitのテストシナリオでは、独自の実装(またはモック/スタブ/フェイク)を提供できます(またはする必要があります) IMyInterfaceのように:

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

そしてあなたのテストでは:

[TestClass]
public class MyControllerTests
{

    MyController _systemUnderTest;
    IMyInterface _myInterface;

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

}

したがって、単体テストの範囲MyControllerの場合、IMyInterfaceの実際の実装は重要ではありません(そしてではないはずです matter)、インターフェース自体のみが重要です。 IMyInterfaceからMyTestClassの「偽の」実装を提供しましたが、MoqまたはRhinoMocksのようなモックでこれを行うこともできます。

要するに、実際にテストを実行するために依存性注入コンテナは必要なく、テストされたクラスの依存性の個別の制御可能な実装/モック/スタブ/フェイクのみが必要です。

21
Kritner

@Kritnerの答えは正しいものの、コードの整合性とより良い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

簡単な方法として、汎用の依存関係リゾルバーヘルパークラスを作成し、ユニットテストクラスにIWebHostを構築しました。

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

ユニットテストプロジェクト

  [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

なぜあなたはそれらをテストクラスに注入したいのですか?通常、たとえば RhinoMocks などのツールを使用してスタブまたはモックを作成することにより、MatchControllerをテストします。以下は、それとMSTestを使用した例で、そこから推定できます。

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