当我在 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
}
}
您可以找到更完整的示例 。
说,我有下面的控制器
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
}
}
您可以找到更完整的示例