说,我有下面的 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/