自动锁定SUT

本文关键字:SUT 锁定 | 更新日期: 2023-09-27 18:15:07

我已经阅读了Mark Seeman关于自动嘲弄的文章,现在我正在根据那篇文章编写一个可重用的windsor容器。

我对Mark文章的实现(基本上是直接复制)

主要工作是在AutoMoqResolver类中完成的。这将在类依赖于接口时提供一个mock:

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

使用以下IWindsorInstaller接口的实现将AutoMoqResolver添加到容器中:

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

My test pattern

因此,为了验证PerformTask()方法是从DoSomethingWithAjax()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 . testableexamplcontroller。找不到无参数构造函数。参数名称: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实现,但我就是不能让它工作。

希望我的问题是清楚的,答案实际上是相对简单的?

自动锁定SUT

结果是AutoFixture。AutoMoq会做我想要的东西,所以感谢TrueWill为我指出了正确的方向。

下面的简单测试将通过:

[TestMethod]
public void Run_Calls_DoSomethingProtected()
{
    //// Arrange
    // AutoMoqCustomization allows AutoFixture to 
    // be used an an auto-mocking container
    var fixture = new Fixture().Customize(new AutoMoqCustomization());
    // simply ask the fixture to create a mock
    var sutMock = fixture.Create<Mock<TestableDummySystem>>();
    //// Act
    // exercise the mock object
    sutMock.Object.Run();
    //// Assert
    // this verification passes!
    sutMock.Verify(x => x.DoSomethingProtectedPublic());
}