我已经阅读了 Mark Seeman关于自动模拟的文章,现在我正在根据该文章编写一个可重复使用的 Windsor 容器。
我对马克文章的实现(基本直接复制)
主要工作在AutoMoqResolver
课堂上完成。每当一个类依赖于接口时,这将提供一个模拟:
public class AutoMoqResolver : ISubDependencyResolver
{
private readonly IKernel kernel;
public AutoMoqResolver(IKernel kernel)
{
this.kernel = kernel;
}
public bool CanResolve(
CreationContext context,
ISubDependencyResolver contextHandlerResolver,
ComponentModel model,
DependencyModel dependency)
{
return dependency.TargetType.IsInterface;
}
public object Resolve(
CreationContext context,
ISubDependencyResolver contextHandlerResolver,
ComponentModel model,
DependencyModel dependency)
{
var mockType = typeof(Mock<>).MakeGenericType(dependency.TargetType);
return ((Mock)this.kernel.Resolve(mockType)).Object;
}
}
AutoMoqResolver
使用以下接口实现将其添加到容器中IWindsorInstaller
:
public class AutoMockInstaller<T> : IWindsorInstaller
{
public void Install(
IWindsorContainer container,
IConfigurationStore store)
{
container.Kernel.Resolver.AddSubResolver(
new AutoMoqResolver(container.Kernel));
container.Register(Component.For(typeof(Mock<>)));
container.Register(Classes
.FromAssemblyContaining<T>()
.Pick()
.WithServiceSelf()
.LifestyleTransient());
}
}
然后我的容器只需运行安装程序,它就可以为单元测试中的任何接口依赖项自动提供模拟:
public class AutoMockContainer<T> : WindsorContainer
{
public AutoMockContainer()
{
// simply run the auto-mock installer
this.Install(new AutoMockInstaller<T>());
}
}
极好的!
我已经对此进行了测试,并且很高兴地自动模拟了我的依赖项,因此我将其应用于一些真实的代码。这是当我意识到解决方案对我没有帮助的时候,因为我在测试课程时倾向于遵循的模式。我的具体问题是我希望能够自动模拟 SUT 本身,以验证 SUT 上的一种方法是否被另一个方法调用。
我需要测试的代码
我将通过一个例子来解释我自己。我正在开发 MVC 代码,并且使用以下通用模式支持不显眼的 AJAX:
public Class ExampleController : Controller
{
private IService service;
public ExampleController(IService service)
{
this.service = service;
}
public PartialViewResult DoSomethingWithAjax()
{
this.PerformTask();
return this.PartialView();
}
public RedirectToRouteResult DoSomethingWithoutAjax()
{
this.PerformTask();
return this.RedirectToAction("SomeAction");
}
protected virtual void PerformTask()
{
// do something here
}
}
我的测试模式
因此,为了验证该PerformTask()
方法是从DoSomethingWithAjax()
or调用的DoSomethingWithoutAjax()
,我定义了一个新TestableExampleController
类,如下所示:
public class TestableExampleController : ExampleController
{
public TestableExampleController(IService service) : base(service)
{
}
public virtual void PerfomTaskPublic()
{
base.PerfomTask();
}
protected override void PerformTask()
{
this.PerformTaskPublic();
}
}
然后我可以TestableExampleController
用作我的 SUT,以便通过以下测试:
[TestMethod]
public void DoSomethingAjax_Calls_PerformTask()
{
//// Arrange
// create a mock TestableExampleController
var controllerMock = new Mock<TestableExampleController>();
controllerMock.CallBase = true;
// use the mock controller as the SUT
var sut = controllerMock.Object;
//// Act
sut.DoSomethingAjax();
//// Assert
controllerMock.Verify(x => x.PerformTaskPublic(), Times.Once());
}
我的问题
像这样重构这个测试以使用我的AutoMockContainer
类是行不通的:
[TestMethod]
public void DoSomethingAjax_Calls_PerformTask()
{
//// Arrange
// create a container
var container = new AutoMockContainer<TestableExampleController>();
// resolve a mock SUT using the container
var controllerMock = container.Resolve<Mock<TestableExampleController>>();
controllerMock .CallBase = true;
// use the mock controller as the SUT
var sut = controllerMock.Object;
//// Act
sut.DoSomethingAjax();
//// Assert
controllerMock.Verify(x => x.PerformTaskPublic(), Times.Once());
}
测试未能创建实例,Mock<TestableExampleController>
因为它找不到无参数构造函数。
无法实例化类的代理:MyNamespace.TestableExampleController。找不到无参数构造函数。参数名称:constructorArguments
我提出的解决方案
理想情况下,我想实现一个包装类,它可以在容器中注册,以自动为任何组件提供模拟:
public class ComponentWrapper<T> where T : class
{
public ComponentWrapper(Mock<T> componentMock)
{
componentMock.CallBase = true;
this.ComponentMock = componentMock;
}
public Mock<T> ComponentMock { get; private set; }
public T Component
{
get { return this.ComponentMock.Object; }
}
}
我希望能够编写以下通过的测试:
[TestMethod]
public void DoSomethingAjax_Calls_PerformTask()
{
//// Arrange
// create a container
var container = new AutoMockContainer<TestableExampleController>();
// resolve a ComponentWrapper using the container
var wrapper = container.Resolve<ComponentWrapper<TestableExampleController>>();
//// Act
// call a method using the component
wrapper.Component.DoSomethingAjax();
//// Assert
// verify a method call using the mock
wrapper.ComponentMock.Verify(x => x.PerformTaskPublic(), Times.Once());
}
我无法完全理解如何实现这一点,并且我大部分时间都在摆弄新的 ISubDependencyResolver 实现,但我就是无法让它工作。
希望我的问题很清楚,答案实际上相对简单?