NSubstitute, утверждает полученные вызовы, аргументы сравниваются с использованием object.ReferenceEquals

Пожалуйста, взгляните на следующий образец:

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

Но это не поддерживается по умолчанию.

Итак, как мне решить эту проблему. Спасибо.


person Peter Larsen 'CPH'    schedule 05.07.2016    source источник


Ответы (3)


На данный момент в NSubstitute нет полной поддержки этого (v1.10). В выпуске 160 есть обсуждение этого вопроса.

Один из вариантов, как упомянул Дэвид Осборн, состоит в том, чтобы перехватывать аргументы и утверждать их, используя выбранную вами библиотеку утверждений.

Другой способ — использовать пользовательский сопоставитель аргументов. Я включил пример из этого комментария:

[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) { ... }

Как описано в комментарии, это работает, но имеет проблемы с предоставлением довольно ужасного сообщения об ошибке при сбое.

Существует также пример подключения NSubstitute к FluentAssertions.

person David Tchepak    schedule 05.07.2016
comment
Что ж, я согласен, сообщение об ошибке не очень красивое, но это обходной путь, и пока он будет работать, пока не будет выпущена лучшая реализация... - person Peter Larsen 'CPH'; 06.07.2016

Вы можете сохранить то, что было передано, а затем сравнить позже:

[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);
}
person David Osborne    schedule 05.07.2016
comment
Это правда, но тогда я все равно не буду знать, получил ли CalledMethod вызов с точными аргументами или нет, верно...! - person Peter Larsen 'CPH'; 06.07.2016
comment
В контексте теста, который вы делаете, потому что methodReceivedThis можно установить только вызовом CalledMethod(). - person David Osborne; 06.07.2016
comment
Если бы я уже знал результат, мне бы не пришлось проверять его, верно :-) Очевидно, в реальном мире я бы не знал, какой метод, который вызывает другой метод, который, возможно, вызывает еще один метод, будет доставить к этому методу, и поэтому я проверяю, какие аргументы я ожидаю получить. - person Peter Larsen 'CPH'; 06.07.2016

Я столкнулся с той же проблемой, это решило ее для меня:

[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
}
person Alex    schedule 27.08.2019