【问题标题】:Managing services fed into MVC controllers by AutoFixture in a generic test helper在通用测试助手中管理由 AutoFixture 输入 MVC 控制器的服务
【发布时间】:2013-09-23 09:21:13
【问题描述】:

我是 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”方法中创建的模拟时,它似乎与注入控制器的模拟版本相同,但不会Verify!

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

    };
}

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

【问题讨论】:

    标签: c# tdd moq autofixture


    【解决方案1】:

    Create 每次都会创建一个新的匿名实例,除非您冻结(通过 .Freeze&lt;T&gt;() 或 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

    【讨论】:

    • 这不太行,'static ISomeService SomeService'应该是'static Mock SomeService',还是'For'方法应该变成一个真实的实例?
    • @Grace:应该是Mock&lt;ISomeService&gt;。固定。
    • 如果测试客户端在调用For 方法之前读取了ControllerUnderTest 属性怎么办?
    • @MarkSeemann:这不应该发生。这就是为什么我将For 调用移动到Because of 并将其值保存到一个字段中。
    • 难道你不能通过冻结在ControllerContext的构造函数中来完全避免时间耦合吗?
    【解决方案2】:

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

    您可以根据需要add Customizations to AutoFixture to allow natural creation of MVC Controllers,然后只需根据需要输入或冻结自定义依赖项即可。

    我强烈建议您花时间更改您的测试结构,让 AutoFixture 以声明方式创建控制器 - 看看 AutoFixture.Xunit 的可能性,并使用它来了解您如何构建测试助手在您的规格中使用。

    (一些背景知识 - 我一直在使用 SubSpec 处理所有这些规范内容,最终对 AutoFixture.Xunit 感到更满意 - 它更简单,更易于组合。

    【讨论】:

    • 我也从带有手动 AutoFixture 的 MSpec 切换到带有自动 AutoFixtre 集成的 xUnit。 +1
    猜你喜欢
    • 2021-05-03
    • 2012-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-22
    • 2013-04-16
    • 1970-01-01
    相关资源
    最近更新 更多