在使用nSubstitute和Autofixture作为DI容器的单元测试中,如何获取模拟对象?

时间:2017-10-05 23:22:58

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

我曾经在单元测试中使用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容器?

2 个答案:

答案 0 :(得分:3)

您可以使用各种动态模拟库(包括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。

答案 1 :(得分:-1)

当您致电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会为所有公共属性生成随机值,这可能是不受欢迎的,因为测试时需要完全控制您所测试的类。