在通用测试帮助程序中管理通过 AutoFixture 馈送到 MVC 控制器的服务
本文关键字:MVC 服务 控制器 测试 帮助程序 管理 AutoFixture | 更新日期: 2023-09-27 18:32:20
我是AutoFixture的新手,正在尝试在我的测试环境中为团队中不太倾向于TDD的开发人员创建一个友好的扩展。这是代码:
public class HomeController : Controller
{
private readonly ISomeService _someService;
public HomeController(ISomeService someService)
{
_someService = someService;
}
public ActionResult Index()
{
_someService.SomeMethod();
return View("Index");
}
}
public class ControllerContext<T> where T : Controller
{
protected static T ControllerUnderTest;
private static IFixture _fixture;
public ControllerContext()
{
_fixture = new Fixture().Customize(new AutoMoqCustomization());
_fixture.Customize<ControllerContext>(c => c.Without(x => x.DisplayMode));
ControllerUnderTest = _fixture.Create<T>();
}
protected static Mock<TDouble> For<TDouble>() where TDouble : class
{
//var mock = _fixture.Create<TDouble>();
var mock = _fixture.Create<Mock<TDouble>>();
return mock;
}
}
所以扩展是For
方法 - 当我检查ControllerUnderTest
哪个注入了"ISomeService"时,它有一个注入的实例,它肯定调用了我断言的方法。当我检查在"For"方法中创建的模拟时,它似乎与注入控制器的版本相同,但它不会Verif
!
public class EXAMPLE_When_going_to_home_page : ControllerContext<HomeController>
{
Because of = () =>
{
ControllerUnderTest.Index();
};
It should_do_something = () =>
{
//This throws a 'Invocation was not performed'
For<ISomeService>().Verify(x => x.SomeMethod());
};
Establish context = () =>
{
};
}
我正在努力寻找有人做类似事情的任何例子,我知道我肯定在这里做一些愚蠢的事情,但在我的脑海中,这个测试应该通过吗?
Create
每次都会创建一个新的匿名实例,除非您冻结(通过.Freeze<T>()
或AutoFixture.Xunit的[Frozen]
)实例。这意味着注入HomeController
的值与 For
返回的值不同。
有几种可能的解决方案,所有这些解决方案最终都将涉及冻结值或注入要使用的值。
一个例子如下所示:
public class ControllerContext<T> where T : Controller
{
private static Lazy<T> _controllerFactory;
private static IFixture _fixture;
public ControllerContext()
{
_fixture = new Fixture().Customize(new AutoMoqCustomization());
_fixture.Customize<ControllerContext>(c => c.Without(x => x.DisplayMode));
_controllerFactory = new Lazy<T>(() => _fixture.Create<T>());
}
protected static Mock<TDouble> For<TDouble>() where TDouble : class
{
var mock = _fixture.Freeze<Mock<TDouble>>();
return mock;
}
protected static T ControllerUnderTest
{
get { return _controllerFactory.Value; }
}
}
public class EXAMPLE_When_going_to_home_page : ControllerContext<HomeController>
{
static Mock<ISomeService> SomeService;
Because of = () =>
{
SomeService = For<ISomeService>();
ControllerUnderTest.Index();
};
It should_do_something = () =>
{
//This throws a 'Invocation was not performed'
SomeService.Verify(x => x.SomeMethod());
};
Establish context = () =>
{
};
}
此更改版本的要点是,首先在服务模拟上调用Freeze
,然后才创建控制器的匿名实例。由于现在使用 For
方法的方式,您可能应该将其重命名为 GetService
。
如果您走上将状态作为管理服务和 SUT 之间交互的一种方式的道路,您最终将陷入一个痛苦的世界static
。一个原因是,例如单元测试应该是可并行化的(例如 xUnit.net v2,但最终所有测试框架都是有意义的)
您可以将自定义添加到 AutoFixture 以允许根据需要自然创建 MVC 控制器,然后只需根据需要输入或冻结自定义依赖项即可。
我强烈建议花时间更改测试的结构,让 AutoFixture 以声明方式创建控制器 - 看看 AutoFixture.Xunit 的功能,并用它来告知您如何构建您在规范中使用的测试帮助程序。
(一些背景 - 我一直在使用SubSpec的所有这些规范的东西,最终对AutoFixture.Xunit更满意 - 它只是更简单,更易于组合。