c# - 在 ASP.NET MVC Controller 中进行 setter 注入(inject)时如何传递模拟对象

标签 c# asp.net-mvc dependency-injection nunit moq

说,我有下面的 Controller

public class UsersController : Controller
{
   private IUsersRepository UsersRepository { get; }
   public UsersController()
   {
       UsersRepository = DependencyResolver.Current.GetService(typeof(IUsersRepository)) as IUsersRepository;
   }
   public ActionResult Index ()
   {
        MyUserDefinedModel data = UsersRepository.MyRepository();
        return View(data);
   }
}

现在我想模拟 IUsersRepository 并将其传递到我的测试脚本中的 Controller 。

下面是我的测试代码

public class UsersListTest
   {
       private UsersController usersController = new Mock<IUsersRepository>();
       private Mock<IUsersRepository> usersRepository = new UsersController();
       [TestMethod]
       public void TestMethod1()
       {
           //usersRepository.Setup(x => x.Get()).Returns(users);
       }
   }

因为 private IUsersRepository UsersRepository { get; private,我无法传递 IUsersRepository 的 mock。

在这种情况下编写单元测试和模拟是什么好主意。

最佳答案

您在测试时遇到问题的原因是因为您的 Controller 类使用了 Service Locator anti-pattern .服务定位器是全局实例(DependencyResolver.Current)或允许在运行时解析依赖关系的抽象。服务定位器的众多缺点之一是它会导致测试问题。

您应该放弃服务定位器模式,改用依赖注入(inject),最好是构造函数注入(inject)。您的应用程序组件应该有一个 single public constructor这些构造函数应该只做 storing the incoming dependencies .这将导致以下 UsersController 实现:

public class UsersController : Controller
{
   private IUsersRepository usersRepository;
   public UsersController(IUsersRepository usersRepository)
   {
       this.usersRepository = usersRepository;
   }
   public ActionResult Index()
   {
        return View(this.usersRepository.MyRepository());
   }
}

有了这个,单元测试就变得微不足道了:

public class UsersControllerTests
{
    [TestMethod]
    public void Index_Always_CallsRepository()
    {
        // Arrange
        var repository = new Mock<IUsersRepository>();
        var controller = CreateValidUsersController(repository.Instance);

        // Act
        var result = controller.Index();

        // Assert
        Assert.IsTrue(repository.IsCalled);
    }

    // Factory method to simplify creation of the class under test with its dependencies
    private UsersController CreateValidUsersController(params object[] deps) {
        return new UsersController(
            deps.OfType<IUsersRepository>().SingleOrDefault() ?? Fake<IUsersRepository>()
            // other dependencies here
            );
    }

    private static T Fake<T>() => (new Mock<T>()).Instance;
}

然而,这确实会迫使您更改 MVC 的默认 IControllerFactory,因为开箱即用,MVC 只能使用默认构造函数处理 Controller 。但这是微不足道的,看起来如下:

public sealed class CompositionRoot : DefaultControllerFactory
{
    private static string connectionString = 
        ConfigurationManager.ConnectionStrings["app"].ConnectionString;

    protected override IController GetControllerInstance(RequestContext _, Type type) {

        if (type == typeof(UsersController))
            return new UsersController(new UsersRepository());

        // [other controllers here]

        return base.GetControllerInstance(_, type);
    }
}

您的新 Controller 工厂可以按如下方式连接到 MVC 中:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start() {
        ControllerBuilder.Current.SetControllerFactory(new CompositionRoot());

        // the usual stuff here
    }
}

您可以找到更完整的示例 here .

关于c# - 在 ASP.NET MVC Controller 中进行 setter 注入(inject)时如何传递模拟对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36741736/

相关文章:

c# - 发送匿名邮件 C#

c# - 可空列表 <> 作为输出参数

javascript - 通过 JSON 将 javascript 参数字典发送到 MVC Controller

java - Spring 轮廓注入(inject): don't inject a class without a profile

c# - IUnityContainer.CreateChildContainer() 抛出 NullReferenceException

c# - 微软依赖注入(inject)文档

c# - 在 C# 运行时添加和删除标签页

c# - 编译时错误 - 无法使用实例引用访问成员

asp.net-mvc - ASP.NET MVC 现在是 "open source"。这是一件好事吗?

asp.net - reCaptcha 验证超时异常