Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSubstitute, assert on received calls, arguments is compared using object.ReferenceEquals

Please take a look at the following sample:

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

And the following test:

    [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
    }

The problem is that I don't know how to test the arguments in the received call (CallMethod). As it is, the arguments is compared using first object.ReferenceEquals and then object.Equals, and since I usually don't have control over the delivered data to the method, the objects (data and expectedResult) never references the same object.

However, there is a way to make it work, and that is if I override Equals, like this:

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

This will work, but I don't want to implement Equals to satisfy a test since it will have all kind of other implications not worth mentioning here.

What I want is a comparer doing the same a the second assert line:

data.ShouldBeEquivalentTo(expectedResult);

But this is not supported per default.

So, how do I solve this problem. Thank you.

like image 870
Peter Larsen 'CPH' Avatar asked Jul 24 '26 00:07

Peter Larsen 'CPH'


2 Answers

NSubstitute doesn't have fully-baked support for this at the moment (v1.10). Issue 160 has some discussion of this.

One option as David Osborne mentioned is to catch the arguments and assert on them using your assertion library of choice.

Another is to use a custom argument matcher. I've included an example from 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) { ... }

As described in the comment, this works but has problems with providing a pretty terrible error message when it fails.

There is also an example of hooking NSubstitute up to FluentAssertions.

like image 148
David Tchepak Avatar answered Jul 25 '26 13:07

David Tchepak


You can store what's passed and then compare later:

[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);
}
like image 36
David Osborne Avatar answered Jul 25 '26 12:07

David Osborne