アプリケーションの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のようなことはできますか?
.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
のようなモックでこれを行うこともできます。
要するに、実際にテストを実行するために依存性注入コンテナは必要なく、テストされたクラスの依存性の個別の制御可能な実装/モック/スタブ/フェイクのみが必要です。
@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>();
}
}
簡単な方法として、汎用の依存関係リゾルバーヘルパークラスを作成し、ユニットテストクラスに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);
}
}
なぜあなたはそれらをテストクラスに注入したいのですか?通常、たとえば 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);
}