我的ASP.NET MVC控制器应该如何识别存储库

时间:2022-03-14 02:17:31

I'm trying to get my head around how one would unit test an ASP.NET MVC project that accesses data through a repository of some sort.

我试图了解如何对通过某种存储库访问数据的ASP.NET MVC项目进行单元测试。

Durring the unit tests I'd obviously want to create a mock repository but how do I pass this mock repository to the Controller instance being tested? Also how would the actual repository, that's really connected to a database, find its way to the controller?

在单元测试中,我显然想创建一个模拟存储库但是如何将这个模拟存储库传递给正在测试的Controller实例?另外,真正连接到数据库的实际存储库如何找到它到控制器的方式?

Do I simply do this through the constructors as I've shown bellow? I think this is how I should set up my controllers, but I'd like some confirmation that this is correct:

我是否只是通过构造函数来完成此操作,如下所示?我认为这是我应该如何设置我的控制器,但我想确认这是正确的:

public class SampleController : Controller
{
    private IRepository _repo;

    //Default constructor uses a real repository
    // new ConcreteRepo() could also be replaced by some static 
    // GetRepository() method somewhere so it would be easy to modify
    //which concrete IRepository is being used
    public SampleController():this(new ConcreteRepo())
    {

    }

    //Unit tests pass in mock repository here
    public SampleController(IRepository repo)
    {
        _repo = repo;
    }
}

4 个解决方案

#1


3  

As everyone has already said, you'll want to use an IoC* or DI** container. But what they haven't said is why this is the case.

正如大家已经说过的那样,你会想要使用IoC *或DI **容器。但他们没有说的是为什么会这样。

The idea is that a DI container will let you bypass ASP.NET MVC's default controller-construction strategy of requiring a parameterless constructor. Thus, you can have your controllers explicitly state their dependencies (as interfaces preferably). How those interfaces map to concrete instances is then the business of the DI container, and is something you will configure in either Global.asax.cs (live) or your test fixture setup (for unit testing).

我们的想法是,DI容器将允许您绕过ASP.NET MVC的默认控制器构造策略,即需要无参数构造函数。因此,您可以让控制器明确说明其依赖关系(最好是接口)。这些接口如何映射到具体实例是DI容器的业务,您可以在Global.asax.cs(实时)或测试夹具设置(用于单元测试)中配置。

This means your controller doesn't need to know anything about concrete implementations of its dependencies, and thus we follow the Dependency Inversion Principle: "High-level modules should not depend on low-level modules. Both should depend on abstractions."

这意味着您的控制器不需要了解其依赖项的具体实现,因此我们遵循依赖性倒置原则:“高级模块不应该依赖于低级模块。两者都应该依赖于抽象。”

For example, if you were to use AutoFac, you would do this:

例如,如果您要使用AutoFac,则执行以下操作:

// In Global.asax.cs's Application_Start
using Autofac;
using Autofac.Integration.Mvc;

var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.Register<IRepository>(() => new ConcreteRepo());
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

// In your unit test:
var controllerInstance = new SampleController(new InMemoryFakeRepo());

// In SampleController
public class SampleController : Controller
{
    private readonly IRepository _repo;

    public SampleController(IRepository repo)
    {
        _repo = repo;
    }

    // No parameterless constructor! This is good; no accidents waiting to happen!
    // No dependency on any particular concrete repo! Excellent!
}

* IoC = inversion of control
** DI = dependency inversion
(the two terms are often used interchangeably, which is not really correct IMO)

* IoC =控制反转** DI =依赖倒置(这两个术语经常互换使用,这不是真正正确的IMO)

#2


3  

Yeah, you're correct, you pass it to your constructor like you have it. By mocking IRepository your explicitly ensuring that the database dependent code doesn't get into the controller for testing, like you want.

是的,你是对的,你把它传递给你的构造函数就像你拥有它一样。通过模拟IRepository,您可以明确地确保数据库相关代码不会像您想要的那样进入控制器进行测试。

When you actually run it, you'll want to setup your application to work with an inversion of control container to enable those dependencies to be injected into your controller (some popular ones are Ninject, StructureMap, and Windsor).

实际运行它时,您需要设置应用程序以使用控件容器的反转,以便将这些依赖项注入控制器(一些常用的是Ninject,StructureMap和Windsor)。

Here's a sample of testing using Moq:

以下是使用Moq进行测试的示例:

private Mock<IRepository> _mockRepo;
private SampleController _controller;

[TestInit]
public void InitTest()
{
    _mockRepo = new Mock<IRepository>();
    _controller = new SampleController(_mockRepo.Object);
}

[Test]
public void Some_test()
{
    _mockRepo.Setup(mr => mr.SomeRepoCall()).Returns(new ValidObject());

    var result = _controller.SomeAction() as ViewResult;
    Assert.IsNotNull(result);
}

Now you can test your actions and mock your IRepository to behave however you want.

现在,您可以测试您的操作并模拟您的IRepository,以便按照您的意愿行事。

#3


0  

The best answer I know is to use an Ioc Container: http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

我知道的最佳答案是使用Ioc容器:http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

I prefer Castle Windsor

我更喜欢Castle Windsor

With the controller dependencies passed in you can then create mocks. We have dependencies that implement interfaces which can be mocked.

通过传入控制器依赖关系,您可以创建模拟。我们有依赖项来实现可以模拟的接口。

#4


0  

For the real one, check out ninject mvc 3 on nuget, for unit testing I prefer to use fake objects with in-memory collections of known data

对于真实的,请查看nuget上的ninject mvc 3,对于单元测试,我更喜欢使用具有已知数据的内存中集合的虚假对象

#1


3  

As everyone has already said, you'll want to use an IoC* or DI** container. But what they haven't said is why this is the case.

正如大家已经说过的那样,你会想要使用IoC *或DI **容器。但他们没有说的是为什么会这样。

The idea is that a DI container will let you bypass ASP.NET MVC's default controller-construction strategy of requiring a parameterless constructor. Thus, you can have your controllers explicitly state their dependencies (as interfaces preferably). How those interfaces map to concrete instances is then the business of the DI container, and is something you will configure in either Global.asax.cs (live) or your test fixture setup (for unit testing).

我们的想法是,DI容器将允许您绕过ASP.NET MVC的默认控制器构造策略,即需要无参数构造函数。因此,您可以让控制器明确说明其依赖关系(最好是接口)。这些接口如何映射到具体实例是DI容器的业务,您可以在Global.asax.cs(实时)或测试夹具设置(用于单元测试)中配置。

This means your controller doesn't need to know anything about concrete implementations of its dependencies, and thus we follow the Dependency Inversion Principle: "High-level modules should not depend on low-level modules. Both should depend on abstractions."

这意味着您的控制器不需要了解其依赖项的具体实现,因此我们遵循依赖性倒置原则:“高级模块不应该依赖于低级模块。两者都应该依赖于抽象。”

For example, if you were to use AutoFac, you would do this:

例如,如果您要使用AutoFac,则执行以下操作:

// In Global.asax.cs's Application_Start
using Autofac;
using Autofac.Integration.Mvc;

var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.Register<IRepository>(() => new ConcreteRepo());
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

// In your unit test:
var controllerInstance = new SampleController(new InMemoryFakeRepo());

// In SampleController
public class SampleController : Controller
{
    private readonly IRepository _repo;

    public SampleController(IRepository repo)
    {
        _repo = repo;
    }

    // No parameterless constructor! This is good; no accidents waiting to happen!
    // No dependency on any particular concrete repo! Excellent!
}

* IoC = inversion of control
** DI = dependency inversion
(the two terms are often used interchangeably, which is not really correct IMO)

* IoC =控制反转** DI =依赖倒置(这两个术语经常互换使用,这不是真正正确的IMO)

#2


3  

Yeah, you're correct, you pass it to your constructor like you have it. By mocking IRepository your explicitly ensuring that the database dependent code doesn't get into the controller for testing, like you want.

是的,你是对的,你把它传递给你的构造函数就像你拥有它一样。通过模拟IRepository,您可以明确地确保数据库相关代码不会像您想要的那样进入控制器进行测试。

When you actually run it, you'll want to setup your application to work with an inversion of control container to enable those dependencies to be injected into your controller (some popular ones are Ninject, StructureMap, and Windsor).

实际运行它时,您需要设置应用程序以使用控件容器的反转,以便将这些依赖项注入控制器(一些常用的是Ninject,StructureMap和Windsor)。

Here's a sample of testing using Moq:

以下是使用Moq进行测试的示例:

private Mock<IRepository> _mockRepo;
private SampleController _controller;

[TestInit]
public void InitTest()
{
    _mockRepo = new Mock<IRepository>();
    _controller = new SampleController(_mockRepo.Object);
}

[Test]
public void Some_test()
{
    _mockRepo.Setup(mr => mr.SomeRepoCall()).Returns(new ValidObject());

    var result = _controller.SomeAction() as ViewResult;
    Assert.IsNotNull(result);
}

Now you can test your actions and mock your IRepository to behave however you want.

现在,您可以测试您的操作并模拟您的IRepository,以便按照您的意愿行事。

#3


0  

The best answer I know is to use an Ioc Container: http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

我知道的最佳答案是使用Ioc容器:http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

I prefer Castle Windsor

我更喜欢Castle Windsor

With the controller dependencies passed in you can then create mocks. We have dependencies that implement interfaces which can be mocked.

通过传入控制器依赖关系,您可以创建模拟。我们有依赖项来实现可以模拟的接口。

#4


0  

For the real one, check out ninject mvc 3 on nuget, for unit testing I prefer to use fake objects with in-memory collections of known data

对于真实的,请查看nuget上的ninject mvc 3,对于单元测试,我更喜欢使用具有已知数据的内存中集合的虚假对象