模拟集 return 类型基于对象的属性而不是它的引用
Mock set return type based on object's properties and not its reference
由于模拟框架 (NSubstitute
) 的工作方式,我的单元测试遇到了问题。
我想测试一个接收参数的方法,在该方法内部,我使用 new 运算符创建一个新对象,并将这个新对象传递给将构建另一个对象的构建器。我的问题是我无法将构建器模拟为 return 我想要的,因为当我配置 return 对象时,它会根据引用进行配置。
因此,如果我新创建的对象如下所示:
class MyReferenceType
{
public String Property1 { get; set; }
public String Property2 { get; set; }
public String Property3 { get; set; }
}
如果我的模拟对象创建一个 MyReferenceType
类型的新对象,我会说
myBuilder.Build(myReferenceTypeObject).Returns(anotherObject);
在我的方法中,对象 myReferenceTypeObject
将有另一个引用,它不会 return 我想要的对象。
那么有没有一种方法可以根据对象的 属性 内容而不是它的引用来配置 mock 的 return 对象?
这是一些代码:
class Mapper
{
private Builder builder;
public Mapper(Builder builder)
{
this.builder = builder;
}
public string Map(string data)
{
//process the string
MyReferenceType obj = new MyReferenceType();
return this.builder.Build(obj);
}
}
无法使引用匹配。您无法在被测方法之外控制该对象,因为它是在方法内初始化的。
对参数使用 Arg.Any<T>()
使模拟期望在执行时更加灵活,因为它会忽略传递的特定参数。
根据提供的代码示例,一个简单的测试看起来像
//Arrange
var data = "some data";
var myBuilder = Substitute.For<Builder>();
var expected = "some value";
myBuilder.Build(Arg.Any<MyReferenceType>()).Returns(expected);
var subject = new Mapper(myBuilder);
//Act
var actual = subject.Map(data);
//Assert
Assert.AreEqual(expected, actual);
这将允许模拟在调用时按预期运行。
如果你想有条件地匹配参数使用Arg.Is<T>(Predicate<T> condition)
myBuilder
.Build(Arg.Is<MyReferenceType>(_ => _.Property1 == "value1" && _.Property2 == "value2"))
.Returns(expected);
如果传递的参数满足预期条件,行为应该与上述预期相同。
这不是问题的答案,而是另一种测试方法,可以消除您面临的问题。
与其创建模拟,不如使用 Builder
的实际实现 - 那么您的测试将很简单
// Arrange
var givenData = "some data";
var expected = "transformed data";
var builder = new Builder();
var mapper = new Mapper(builder);
// Act
var actual = mapper.Map(data);
// Assert
actual.Should().Be(expected);
使用 Builder
的实际实现将使您有可能在 Map
方法内部进行更改,并在不更改测试的情况下对 Builder
进行更改。
仅模拟 classes/methods 这会使您的测试变慢。
由于模拟框架 (NSubstitute
) 的工作方式,我的单元测试遇到了问题。
我想测试一个接收参数的方法,在该方法内部,我使用 new 运算符创建一个新对象,并将这个新对象传递给将构建另一个对象的构建器。我的问题是我无法将构建器模拟为 return 我想要的,因为当我配置 return 对象时,它会根据引用进行配置。
因此,如果我新创建的对象如下所示:
class MyReferenceType
{
public String Property1 { get; set; }
public String Property2 { get; set; }
public String Property3 { get; set; }
}
如果我的模拟对象创建一个 MyReferenceType
类型的新对象,我会说
myBuilder.Build(myReferenceTypeObject).Returns(anotherObject);
在我的方法中,对象 myReferenceTypeObject
将有另一个引用,它不会 return 我想要的对象。
那么有没有一种方法可以根据对象的 属性 内容而不是它的引用来配置 mock 的 return 对象?
这是一些代码:
class Mapper
{
private Builder builder;
public Mapper(Builder builder)
{
this.builder = builder;
}
public string Map(string data)
{
//process the string
MyReferenceType obj = new MyReferenceType();
return this.builder.Build(obj);
}
}
无法使引用匹配。您无法在被测方法之外控制该对象,因为它是在方法内初始化的。
对参数使用 Arg.Any<T>()
使模拟期望在执行时更加灵活,因为它会忽略传递的特定参数。
根据提供的代码示例,一个简单的测试看起来像
//Arrange
var data = "some data";
var myBuilder = Substitute.For<Builder>();
var expected = "some value";
myBuilder.Build(Arg.Any<MyReferenceType>()).Returns(expected);
var subject = new Mapper(myBuilder);
//Act
var actual = subject.Map(data);
//Assert
Assert.AreEqual(expected, actual);
这将允许模拟在调用时按预期运行。
如果你想有条件地匹配参数使用Arg.Is<T>(Predicate<T> condition)
myBuilder
.Build(Arg.Is<MyReferenceType>(_ => _.Property1 == "value1" && _.Property2 == "value2"))
.Returns(expected);
如果传递的参数满足预期条件,行为应该与上述预期相同。
这不是问题的答案,而是另一种测试方法,可以消除您面临的问题。
与其创建模拟,不如使用 Builder
的实际实现 - 那么您的测试将很简单
// Arrange
var givenData = "some data";
var expected = "transformed data";
var builder = new Builder();
var mapper = new Mapper(builder);
// Act
var actual = mapper.Map(data);
// Assert
actual.Should().Be(expected);
使用 Builder
的实际实现将使您有可能在 Map
方法内部进行更改,并在不更改测试的情况下对 Builder
进行更改。
仅模拟 classes/methods 这会使您的测试变慢。