簡體   English   中英

在使用nSubstitute和Autofixture作為DI容器的單元測試中,如何獲取模擬對象?

[英]In unit test when using nSubstitute and Autofixture as a DI container, how to get mocked object?

我曾經在單元測試中使用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容器來執行此操作?

您可以使用各種動態模擬庫(包括NSubstitute)將AutoFixture變成自動模擬容器

重寫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)。

當您將Autofixture稱為Dependency Injection容器時,我很少感到困惑。 它不是。

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會為所有公共屬性生成隨機值,這是不希望的,因為對於測試,您需要完全控制被測類。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM