如何使用构造函数依赖注入对asp.net核心应用程序进行单元测试

如何使用构造函数依赖注入对asp.net核心应用程序进行单元测试

问题描述:

我有一个asp.net核心应用程序,它使用了应用程序的startup.cs类中定义的依赖注入:

I have a asp.net core application that uses dependency injection defined in the startup.cs class of the 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>();

这允许这样的事情:

[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.因此,具有这些接口作为参数的类将为空.

This is very neat. But becomes a problem when I want to unit test. Because my test library does not have a startup.cs where I setup dependency injection. So a class with these interfaces as params will just be 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.:(

In the code above, in the test library, _matchRepository and _teamRepository, will just be null. :(

我可以做一些类似 ConfigureServices 的事情,在我的测试库项目中定义依赖注入吗?

Can I do something like ConfigureServices, where I define dependency injection in my test library project?

您在 .net core 中的控制器从一开始就考虑了依赖注入,但这并不意味着您必须使用依赖注入容器.

Your controllers in .net core have dependency injection in mind from the start, but this does not mean you are required to use a dependency injection container.

给定一个更简单的类,例如:

Given a simpler class like:

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 实例的唯一方法.

So in your app, you're using the dependency injection container in your startup.cs, which does nothing more than provide a concretion of MyClass to use when IMyInterface is encountered. This does not mean it is the only way of getting instances of MyController however.

单元测试场景中,您可以(并且应该)提供您自己的IMyInterface实现(或模拟/存根/伪造)如此:

In a unit testing scenario, you can (and should) provide your own implementation (or mock/stub/fake) of IMyInterface as so:

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的实际实现并不重要(而且不应该强> 问题),只有界面本身很重要.我们通过 MyTestClass 提供了 IMyInterface 的假"实现,但您也可以通过 Moq 使用模拟来实现RhinoMocks.

So for the scope of unit testing MyController, the actual implementation of IMyInterface does not matter (and should not matter), only the interface itself matters. We have provided a "fake" implementation of IMyInterface through MyTestClass, but you could also do this with a mock like through Moq or RhinoMocks.

最重要的是,您实际上不需要依赖注入容器来完成您的测试,只需要一个单独的、可控的、实现/模拟/存根/伪造的测试类依赖项.

Bottom line, you do not actually need the dependency injection container to accomplish your tests, only a separate, controllable, implementation/mock/stub/fake of your tested classes dependencies.