Mock根据对象的属性设置返回类型,而不是它的引用

时间:2018-04-04 08:25:59

标签: c# unit-testing mocking nsubstitute

由于模拟框架(NSubstitute)的工作方式,我在单元测试中遇到问题。

我想测试一个接收参数的方法,在该方法内部我使用new运算符创建一个新对象,并且我将这个新对象传递给将构建另一个对象的构建器。我的问题是我无法模仿构建器来返回我想要的东西,因为当我配置返回对象时,它将根据引用来完成。

因此,如果我新建的对象如下所示:

class MyReferenceType
{
    public String Property1 { get; set; }

    public String Property2 { get; set; }

    public String Property3 { get; set; }
}

如果我的模拟我是否会创建MyReferenceType类型的新对象,我会说

myBuilder.Build(myReferenceTypeObject).Returns(anotherObject);

在我的方法中,对象myReferenceTypeObject将有另一个引用,它不会返回我想要的对象。

那么有没有办法根据对象的属性内容配置模拟的返回对象而不是它的引用?

以下是一些代码:

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);
    }
}

2 个答案:

答案 0 :(得分:2)

无法获得匹配的引用。在测试方法之外,您无法控制该对象,因为它正在方法中进行初始化。

使用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);

如果传递的参数满足预期条件,则行为应与上述预期相同。

参考NSubstitute: Argument matchers

答案 1 :(得分:0)

这不是问题的答案,而是另一种测试方法,它可以消除您面临的问题。

而不是创建模拟使用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

只模拟使你的测试变慢的类/方法。