【问题标题】:NSubstitute, assert on received calls, arguments is compared using object.ReferenceEqualsNSubstitute,在收到调用时断言,使用 object.ReferenceEquals 比较参数
【发布时间】:2016-07-05 10:32:10
【问题描述】:

请看以下示例:

    public interface IDomainClass
    {
        int A { get; set; }
        void CalledMethod(IDomainClass data);
    }
    public class DomainClass : IDomainClass
    {
        public int A { get; set; }

        public void CalledMethod(IDomainClass data)
        {
            throw new NotImplementedException();
        }
    }

还有以下测试:

    [Test]
    public void TestSample()
    {
        //Arrange
        IDomainClass testingClass = Substitute.For<IDomainClass>();

        IDomainClass data = new DomainClass() { A = 123, };
        IDomainClass expectedResult = new DomainClass() { A = 123, };

        //Act
        testingClass.CalledMethod(data);

        //Assert
        testingClass.ReceivedWithAnyArgs(1).CalledMethod(null); //ok
        data.ShouldBeEquivalentTo(expectedResult);              //ok
        testingClass.Received(1).CalledMethod(expectedResult);  //fail
    }

问题是我不知道如何测试接收到的调用(CallMethod)中的参数。实际上,使用第一个 object.ReferenceEquals 然后 object.Equals 比较参数,并且由于我通常无法控制传递给该方法的数据,因此对象(数据和预期结果)永远不会引用同一个对象。

但是,有一种方法可以让它工作,那就是如果我覆盖 Equals,就像这样:

        public override bool Equals(object obj)
        {
            return this.A.Equals((obj as DomainClass).A);
        }
        public override int GetHashCode()
        {
            return this.A.GetHashCode();
        }

这可行,但我不想实现 Equals 来满足测试,因为它会产生各种其他含义,这里不值一提。

我想要的是一个比较器在第二个断言行中做同样的事情:

data.ShouldBeEquivalentTo(expectedResult);

但默认情况下不支持此功能。

那么,我该如何解决这个问题。 谢谢。

【问题讨论】:

    标签: .net unit-testing nsubstitute


    【解决方案1】:

    NSubstitute 目前还没有完全支持此功能 (v1.10)。 Issue 160 对此进行了一些讨论。

    David Osborne 提到的一个选项是捕获参数并使用您选择的断言库对它们进行断言。

    另一个是使用自定义参数匹配器。我已经包含了来自this comment 的示例:

    [Test]
    public void UsingArgMatcher() {
        var repos = Substitute.For<IRepos>();
    
        var sut = new SomeService(repos);
        sut.Process();
    
        repos.Received().Save(Arg.Is(EquivalentTo(new int[] { 1, 2, 3 })));
    }
    
    private Expression<Predicate<IEnumerable<T>>> EquivalentTo<T>(IEnumerable<T> enumerable) {
        return x => Equiv(enumerable, x);
    }
    
    private bool Equiv<T>(IEnumerable<T> a, IEnumerable<T> b) { ... }
    

    如评论中所述,这可行,但在失败时提供非常糟糕的错误消息存在问题。

    还有hooking NSubstitute up to FluentAssertions 的例子。

    【讨论】:

    • 好吧,我同意,错误消息并不漂亮,但它是一种变通方法,现在可以解决,直到发布更好的实现...
    【解决方案2】:

    您可以存储传递的内容,然后再进行比较:

    [Test]
    public void TestSample()
    {
        //Arrange
        IDomainClass testingClass = Substitute.For<IDomainClass>();
    
        IDomainClass data = new DomainClass() { A = 123, };
    
        IDomainClass methodReceievedThis = null;
    
        testingClass
            .When(t => t.CalledMethod(Arg.Any<IDomainClass>())
            .Do(p => methodReceievedThis = p);
    
        //Act
        testingClass.CalledMethod(data);
    
        //Assert
        testingClass.ReceivedWithAnyArgs(1).CalledMethod(null); //ok
    
        methodReceievedThis.ShouldBeEquivalentTo(data);
    }
    

    【讨论】:

    • 确实如此,但是我仍然不知道 CalledMethod 是否确实收到了带有确切参数的调用,对吧...!
    • 在你做的测试的上下文中,因为methodReceivedThis只能通过调用CalledMethod()来设置。
    • 如果我已经知道结果,那么我就不必测试它了,对吧 :-) 很明显,在现实世界中,我不知道什么方法——调用另一个方法,这可能会调用另一种方法 - 将传递给该方法,这就是为什么我测试我希望它接收哪些参数。
    【解决方案3】:

    我也遇到了同样的问题,这解决了我:

    [Test]
    public void TestSample()
    {
        //Arrange
        IDomainClass testingClass = Substitute.For<IDomainClass>();
    
        IDomainClass data = new DomainClass() { A = 123, };
    
        //Act
        testingClass.CalledMethod(data);
    
        //Assert
        testingClass.Received(1).CalledMethod(Arg.Is<DomainClass>(x => x.A == 123));  //this worked for me
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多