2

我是 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 = () =>
    {

    };
}

我正在努力寻找任何人做类似事情的例子,我知道我在这里肯定做了一些愚蠢的事情,但在我看来,这个测试应该通过吗?

4

2 回答 2

5

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.

于 2013-09-23T09:26:33.807 回答
1

static如果您将状态作为管理服务与 SUT 之间交互的一种方式,您最终将陷入痛苦的世界。一个原因是例如单元测试应该是可并行化的(例如 xUnit.net v2,但最终所有测试框架都是有意义的)

您可以将自定义添加到 AutoFixture 以允许根据需要自然创建 MVC 控制器,然后只需根据需要输入或冻结自定义依赖项即可。

我强烈建议你花时间改变你的测试结构,让 AutoFixture 以声明方式创建控制器 - 看看AutoFixture.Xunit有什么可能,并用它来了解你如何构建你在你的眼镜。

(一些背景 - 我一直在使用SubSpec处理所有这些 Spec 的东西,最终对 AutoFixture.Xunit 更满意 - 它更简单,更可组合。

于 2013-10-06T08:18:28.387 回答