在使用 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 容器来做到这一点?
当您调用 Autofixture
依赖注入容器时,我有点困惑。不是。
AutoFixture
只是生成 values/instances 你在当前测试中不关心,但它们不能保持默认值。
在您的情况下,您正确地创建了 IPromise
的模拟,但被测试的 class 不知道它。 AutoFixture 已生成 "own" 个实例并将其传递给正在测试的 class。
您应该手动创建 class 待测实例
- 因为您需要完全控制所有依赖项
- 出于文档原因,其他开发人员可以看到对象是如何创建的
您可以将 AutoFixture 用于您在特定测试中不关心的依赖项,但为了让 class 在测试下正常工作,此依赖项应该 return 一些值(不是空值或默认值) .
// 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);
对于多个测试,您可以引入一些工厂 class,它在测试下创建 class 的实例,并将使用的依赖项公开为 public 属性,因此您可以在测试。
或者使用依赖项作为测试 class 的私有成员,因此所有测试都可以访问它们。
当然,AutoFixture 提供了配置可能性。因此,当 fixture
询问某些类型时,您可以配置为始终 return "your" 模拟
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 创建 class 测试时,您应该小心,因为 AutFixture 将为所有 public 属性生成随机值,这可能是不希望的,因为对于测试,您需要完全控制你的 class 正在接受测试。
您可以使用各种动态模拟库(包括 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
也 returns 它刚刚冻结的对象,这使您可以轻松地将 'dotting' 保留在其中 - 在这种情况下使用 NSubstitute 的 API.
我以前在单元测试中使用 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 容器来做到这一点?
当您调用 Autofixture
依赖注入容器时,我有点困惑。不是。
AutoFixture
只是生成 values/instances 你在当前测试中不关心,但它们不能保持默认值。
在您的情况下,您正确地创建了 IPromise
的模拟,但被测试的 class 不知道它。 AutoFixture 已生成 "own" 个实例并将其传递给正在测试的 class。
您应该手动创建 class 待测实例 - 因为您需要完全控制所有依赖项 - 出于文档原因,其他开发人员可以看到对象是如何创建的
您可以将 AutoFixture 用于您在特定测试中不关心的依赖项,但为了让 class 在测试下正常工作,此依赖项应该 return 一些值(不是空值或默认值) .
// 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);
对于多个测试,您可以引入一些工厂 class,它在测试下创建 class 的实例,并将使用的依赖项公开为 public 属性,因此您可以在测试。
或者使用依赖项作为测试 class 的私有成员,因此所有测试都可以访问它们。
当然,AutoFixture 提供了配置可能性。因此,当 fixture
询问某些类型时,您可以配置为始终 return "your" 模拟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 创建 class 测试时,您应该小心,因为 AutFixture 将为所有 public 属性生成随机值,这可能是不希望的,因为对于测试,您需要完全控制你的 class 正在接受测试。
您可以使用各种动态模拟库(包括 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
也 returns 它刚刚冻结的对象,这使您可以轻松地将 'dotting' 保留在其中 - 在这种情况下使用 NSubstitute 的 API.