在构建我最新的 ASP.NET MVC 项目时,我开始涉足单元测试、依赖注入和所有这些爵士乐。

我现在想要对我的控制器进行单元测试,但我很难弄清楚如何在没有 IoC 容器的情况下正确地执行此操作。

以一个简单的控制器为例:

public class QuestionsController : ControllerBase
{
    private IQuestionsRepository _repository = new SqlQuestionsRepository();

    // ... Continue with various controller actions
}

由于该类是 SqlQuestionsRepository 的直接实例化,因此其单元测试性不太好。因此,让我们沿着依赖注入路线进行:

public class QuestionsController : ControllerBase
{
    private IQuestionsRepository _repository;

    public QuestionsController(IQuestionsRepository repository)
    {
        _repository = repository;
    }
}

这似乎更好。我现在可以使用模拟 IQuestionsRepository 轻松编写单元测试。但是,现在要实例化控制器吗?调用链上游的某个地方,SqlQuestionRepository 必须被实例化。看来我只是将问题转移到其他地方,而不是摆脱它。

现在,我知道这是一个很好的例子,说明 IoC 容器可以通过为我连接控制器依赖项来帮助您,同时保持我的控制器易于进行单元测试。

我的问题是,如何对这种性质的事物进行单元测试 没有 IoC 容器?

笔记:我并不反对 IoC 容器,而且我可能很快就会走这条路。然而,我很好奇对于不使用它们的人来说有什么替代方案。

有帮助吗?

解决方案

是不是能够保持场的直接实例,并且还提供了二传手?在这种情况下,你只能调用单元测试过程中的制定者。是这样的:

public class QuestionsController : ControllerBase
{
    private IQuestionsRepository _repository = new SqlQuestionsRepository();

    // Really only called during unit testing...
    public QuestionsController(IQuestionsRepository repository)
    {
        _repository = repository;
    }
}

我不是太熟悉.NET但作为Java的一个侧面说明这是重构现有的代码,以提高可测试性的常用方法。也就是说,如果您有已经投入使用,需要对其进行修改,以提高代码覆盖率不打破现有的功能。班

我们的团队已经做之前,通常我们设置二传手打包和私营部门,并保持测试类的包一样的,所以它可以调用setter方法的可视性。

其他提示

您可以与您的控制器默认的构造函数,将有某种默认行为。

喜欢的东西...

public QuestionsController()
    : this(new QuestionsRepository())
{
}

这样,在默认情况下,当控制器工厂创建它将使用默认的构造函数的行为控制器的新实例。然后在单元测试中,你可以使用模拟框架中一个模拟传递到其他构造。

一个选项是使用假货。

public class FakeQuestionsRepository : IQuestionsRepository {
    public FakeQuestionsRepository() { } //simple constructor
    //implement the interface, without going to the database
}

[TestFixture] public class QuestionsControllerTest {
    [Test] public void should_be_able_to_instantiate_the_controller() {
        //setup the scenario
        var repository = new FakeQuestionsRepository();
        var controller = new QuestionsController(repository);
        //assert some things on the controller
    }
}

另一个选项是使用嘲笑和一个模拟框架,其可以自动动态生成这些嘲笑。

[TestFixture] public class QuestionsControllerTest {
    [Test] public void should_be_able_to_instantiate_the_controller() {
        //setup the scenario
        var repositoryMock = new Moq.Mock<IQuestionsRepository>();
        repositoryMock
            .SetupGet(o => o.FirstQuestion)
            .Returns(new Question { X = 10 });
        //repositoryMock.Object is of type IQuestionsRepository:
        var controller = new QuestionsController(repositoryMock.Object);
        //assert some things on the controller
    }
}

关于其中所有对象得到构建。在单元测试中,你只设置了一组最少的对象:一个真实的对象,它是测试,以及一些伪造或嘲笑这下测试的真正对象需要依赖。例如,测试下的真正目的是QuestionsController的一个实例 - 其对IQuestionsRepository的依赖,所以我们给它或者是假IQuestionsRepository象在第一实例中,或者是模拟IQuestionsRepository像在第二示例

在实际系统中,但是,您可以设置整个容器的软件的最高层。在Web应用中,例如,将设置在容器,布线了所有的接口和实现类的,在GlobalApplication.Application_Start

我对彼得的回答进行了一些扩展。

在具有大量实体类型的应用程序中,控制器需要引用多个存储库、服务等并不罕见。我发现在测试代码中手动传递所有这些依赖项很乏味(特别是因为给定的测试可能只涉及其中的一两个)。在这些场景中,我更喜欢 setter 注入样式 IOC 而不是构造函数注入。我使用的模式是这样的:

public class QuestionsController : ControllerBase
{
    private IQuestionsRepository Repository 
    {
        get { return _repo ?? (_repo = IoC.GetInstance<IQuestionsRepository>()); }
        set { _repo = value; }
    }
    private IQuestionsRepository _repo;

    // Don't need anything fancy in the ctor
    public QuestionsController()
    {
    }
}

代替 IoC.GetInstance<> 无论您的特定 IOC 框架使用什么语法。

在生产使用中,没有任何内容会调用属性设置器,因此第一次调用 getter 时,控制器将调用您的 IOC 框架,获取一个实例并存储它。

在测试中,您只需在调用任何控制器方法之前调用 setter:

var controller = new QuestionsController { 
    Repository = MakeANewMockHoweverYouNormallyDo(...); 
}

恕我直言,这种方法的好处是:

  1. 在生产中仍然利用IOC。
  2. 在测试期间更轻松地手动构建控制器。您只需要初始化测试实际使用的依赖项。
  3. 如果您不想手动配置常见依赖项,则可以创建特定于测试的 IOC 配置。
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top