【问题标题】:In unit test when using nSubstitute and Autofixture as a DI container, how to get mocked object?在使用 nSubstitute 和 Autofixture 作为 DI 容器的单元测试中,如何获取模拟对象?
【发布时间】:2017-10-05 23:22:57
【问题描述】:

我曾经在单元测试中使用 Moq 和 AutoMoqer,但我的团队决定改用 NSubstitute。我们大量使用 DI,所以我希望能够要求一个目标进行测试,并让该目标自动将所有模拟对象提供给它的构造函数,或者换句话说,一个传入模拟的 DI 容器。我还想根据需要修改那些模拟对象。

使用 Moq/AutoMoq/MSTest 的示例

[TestMethod]
public void ReturnSomeMethod_WithDependenciesInjectedAndD1Configured_ReturnsConfiguredValue()
{
    const int expected = 3;
    var diContainer = new AutoMoq.AutoMoqer();

    var mockedObj = diContainer.GetMock<IDependency1>();
    mockedObj
        .Setup(mock => mock.SomeMethod())
        .Returns(expected);

    var target = diContainer.Resolve<MyClass>();
    int actual = target.ReturnSomeMethod();

    Assert.AreEqual(actual, expected);
}

public interface IDependency1
{
    int SomeMethod();
}

public interface IDependency2
{
    int NotUsedInOurExample();
}

public class MyClass
{
    private readonly IDependency1 _d1;
    private readonly IDependency2 _d2;

    //please imagine this has a bunch of dependencies, not just two
    public MyClass(IDependency1 d1, IDependency2 d2)
    {
        _d1 = d1;
        _d2 = d2;
    }

    public int ReturnSomeMethod()
    {
        return _d1.SomeMethod();
    }
}

由于我的问题措辞不当并且我进行了更多研究,因此我找到了一种使用 NSubstitute/AutofacContrib.NSubstitute/XUnit 的方法:

[Fact]
public void ReturnSomeMethod_WithDependenciesInjectedAndD1Configured_ReturnsConfiguredValue()
{
    const int expected = 3;
    var autoSubstitute = new AutoSubstitute();
    autoSubstitute.Resolve<IDependency1>().SomeMethod().Returns(expected);

    var target = autoSubstitute.Resolve<MyClass>();
    int actual = target.ReturnSomeMethod();

    Assert.Equal(actual, expected);
}

public interface IDependency1
{
    int SomeMethod();
}

public interface IDependency2
{
    int NotUsedInOurExample();
}

public class MyClass
{
    private readonly IDependency1 _d1;
    private readonly IDependency2 _d2;

    //please imagine this has a bunch of dependencies, not just two
    public MyClass(IDependency1 d1, IDependency2 d2)
    {
        _d1 = d1;
        _d2 = d2;
    }

    public int ReturnSomeMethod()
    {
        return _d1.SomeMethod();
    }
}

我还有我原来的问题。如何使用 AutoFixture.AutoNSubstitute 作为 DI 容器来做到这一点?

【问题讨论】:

  • 你有fixture.Create&lt;IPromise&gt;(),还有Substitute.For&lt;IPromise&gt;()。为什么要以两种不同的方式创建两个相同类型的对象? _target 您的系统在测试中吗? IPromise 是如何定义的?
  • 如果您发布 Minimal, Complete, and Verifiable example 会很有帮助 - 例如,您想通过但当前失败的单元测试。另一个想法是,如果您以前有一个使用 AutoFixture.AutoMoq 的有效解决方案,您可以(也)发布它。
  • 嗨,马克 - 感谢您查看此代码。我为这个糟糕的例子道歉,我已经用你的建议更新了这个问题。
  • 什么是AutoMoq.AutoMoqer
  • 来自 nuget:“自动模拟容器”...“AutoMoqer 是一个“自动模拟”容器,可为您创建对象。只需告诉它要创建什么类,它就会创建它。” github.com/darrencauthon/AutoMoq

标签: c# unit-testing dependency-injection autofixture nsubstitute


【解决方案1】:

您可以使用各种动态模拟库(包括 NSubstitute)将 AutoFixture 转换为 Auto-Mocking Container

重写 OP 测试就这么简单:

[Fact]
public void ReturnSomeMethod_UsingAutoFixtureAutoNSubstitute()
{
    const int expected = 3;
    var fixture = new Fixture().Customize(new AutoNSubstituteCustomization());
    fixture.Freeze<IDependency1>().SomeMethod().Returns(expected);

    var target = fixture.Create<MyClass>();
    var actual = target.ReturnSomeMethod();

    Assert.Equal(actual, expected);
}

这里,我用xUnit.net代替了MSTest,但翻译起来应该很简单。

这里的关键是Freeze 方法,它本质上将类型参数的生命周期从transient 更改为singleton。这意味着在调用Freeze 之后,所有其他时间特定的Fixture 实例必须创建一个IDependency 对象,它将重用它冻结的对象。

Freeze 还返回它刚刚冻结的对象,这使您可以轻松地继续“打点”它——在这种情况下,通过使用 NSubstitute 的 API。

【讨论】:

    【解决方案2】:

    当您将 Autofixture 称为依赖注入容器时,我有点困惑。不是。

    AutoFixture 只是生成您在当前测试中不关心的值/实例,但它们不能保存默认值。

    在您的情况下,您正确创建了 IPromise 的模拟,但被测类不知道它。 AutoFixture 已生成“自己的”实例并将其传递给被测类。

    您应该手动创建被测类的实例 - 因为您需要完全控制所有依赖项 - 出于文档原因,其他开发人员可以看到对象是如何创建的

    您可以将 AutoFixture 用于您在特定测试中不关心的依赖项,但为了让被测类正常工作,此依赖项应该返回一些值(不是 null 或默认值)。

    // Arrange
    var fixture = New Fixture();
    
    var input = fixture.Create<int>(); // Will generate some integer value
    var expected = fixture.Create<string>();
    
    var dummyDependency = fixture.Create<IPromiseOne>(); // Don't care about in this test
    var requiredDependency = Substitute.For<IPromiseTwo>(); // Dependency required in the test
    
    // return "expected" only when "input" given
    requiredDependency.SomeFunction(input).Returns(expected); 
    
    var classUnderTest = new ClassUnderTest(requiredDependency, dependencyTwo);
    
    // Act
    var actual = classUnderTest.Convert(input);
    
    // Assert
    actual.Should().Be(expected);
    

    对于多个测试,您可以引入一些工厂类,这些工厂类在测试下创建类的实例并将使用的依赖项公开为公共属性,因此您可以在测试中访问它们。
    或者使用依赖项作为测试类的私有成员,所以所有测试都可以访问它们。

    当然,AutoFixture 提供了配置可能性。因此,您可以配置为在夹具询问某种类型时始终返回“您的”模拟

    var fixture = new Fixture();
    
    var myMock = Substitute.For<IPromise>();
    myMock.SomeFunction().Returns(default(ReturnClass)) // default value is null
    
    // Configure fixture to always return your mock
    fixture.Register<IPromise>(() => myMock);
    
    var classUnderTest = fixture.Create<ClassUnderTest>();
    
    // Execute test
    

    但是,在使用 AutoFixture 创建待测类时应小心,因为 AutFixture 将为所有公共属性生成随机值,这可能是不受欢迎的,因为要进行测试,您需要完全控制待测类。

    【讨论】:

    • 感谢您的帮助,但我认为这不是我想要的。我引用 AutoFixture.AutoNSubstitute。从它的描述来看:“这个扩展把 AutoFixture 变成了一个自动模拟容器。模拟实例是由 NSubstitute 创建的”
    • @Mario,即便如此,想法保持不变,AutoFixture.AutoNSubstitute 将为所有依赖项创建 NSubstitute 代理的对应实例,但在测试中您需要访问那些“生成的”NSubstitute 代理来配置它们对于特定的测试用例。
    猜你喜欢
    • 2017-03-24
    • 1970-01-01
    • 1970-01-01
    • 2015-12-12
    • 2021-08-26
    • 2016-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多