当我在 ASP.NET MVC 控制器中进行 setter 注入时,如何传递模拟对象

How can I pass a mock object when I am doing setter injection in ASP.NET MVC 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 并将其传递到我的测试脚本中的控制器。

下面是我的测试代码

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。

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

您可以添加一个允许您提供 IUsersRepository 模拟的构造函数。您的默认构造函数将使用 DependencyResolver 中的实例调用它,如下所示:

public class UsersController : Controller
{
   private IUsersRepository UsersRepository { get; }

    public UsersController(IUsersRepository usersRepository)
    {
        UsersRepository = usersRepository;
    }

    public UsersController():this(DependencyResolver.Current.GetService(typeof(IUsersRepository)) as IUsersRepository)
    {

    }

    public ActionResult Index ()
    {
       MyUserDefinedModel data = UsersRepository.MyRepository();
       return View(data);
    }

}

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

您应该放弃服务定位器模式,改用依赖注入,最好是构造函数注入。您的应用程序组件应该有一个 single public constructor and those constructors should do nothing more than 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 只能使用默认构造函数处理控制器。但这是微不足道的,看起来如下:

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);
    }
}

您的新控制器工厂可以挂接到 MVC 中,如下所示:

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

        // the usual stuff here
    }
}

您可以找到更完整的示例