Есть ли способ кэшировать определение Arg.Is<> для использования в частях "Arrange" и "Act" теста?

У меня есть тест, который выглядит так:

    [Test]
    public void Blah()
    {
        // Arrange
        // ...
        var thing = new Thing();
        mockRouter.Route(Arg.Is<Transition<Thing>>(x => x != null && x.Subject != null && x.Subject.Equals(thing)));

        // Act
        var result = handler.Handle(thing);

        // Assert
        mockRouter.Received(1).Route(Arg.Is<Transition<Thing>>(x => x != null && x.Subject != null && x.Subject.Equals(thing)));
    }

Я хотел бы кешировать Arg определение в локальной переменной, поэтому я могу использовать его в assert. Суть в том, чтобы уменьшить количество кода в тесте и сделать его более плавным.

    [Test]
    public void Blah()
    {
        // Arrange
        var thing = new Thing();
        var transitionForThing = Arg.Is<Transition<Thing>>(x => x != null && x.Subject != null && x.Subject.Equals(thing));
        mockRouter.Route(transitionForThing);
        // ...

        // Act
        var result = handler.Handle(thing);

        // Assert
        mockRouter.Received(1).Route(transitionForThing);
    }

Это не похоже на работу, так как значение transitionForThing является нулевым, и поэтому утверждение не может сказать, что Received(null) не был назван. Есть ли способ сделать это или что-то подобное, или я застрял с этим синтаксисом?

2 ответа

Решение

Arg.Is имеет параметр типа

Expression<Predicate<T>>

так что вы можете определить его для повторного использования

Expression<Predicate<Transition<Thing>>> predicate = 
  x => x != null && x.Subject != null && x.Subject.Equals(thing));

и использовать его как

mockRouter.Route(predicate);

Но я действительно не понимаю вашу ситуацию: вы обычно высмеиваете классы, которые возвращают какой-то результат, который вам нужен. Я думаю, что в вашем случае вам нужно только проверить, что метод mocked-класса был вызван, вам не нужно определять макет для действия.

Значение, возвращаемое Arg.Is(...) и другие Arg методы имеют сравнительно небольшое значение. Важным моментом является вызов самого метода.

Каждый раз Arg.Is(...) называется NSubstitute, принимает к сведению, что вы попытались указать аргумент, и использует эту информацию для уточнения деталей вызова, который вы указываете. Если вы кешируете значение (которое будет просто default(T)), NSubstitute не будет знать, что вы хотите сопоставить один и тот же аргумент.

Я могу объяснить это больше, если вам интересно, но важно отметить, что вам нужно позвонить Arg.Is(...) каждый раз, когда вы указываете вызов (и в правильном порядке, ожидаемом для вызова).

Чтобы повторно использовать логику сопоставления, я бы извлек предикат в новый метод (как предложил DaniCE) или создал метод, который вызывает Arg.Is и используйте это с осторожностью.

[Test]
public void Blah() {
    var thing = new Thing();
    mockRouter.Route(Arg.Is<Transition<Thing>>(x => HasSubject(x, thing)));
    // ...
    var result = handler.Handle(thing);
    // ...
    mockRouter.Received(1).Route(Arg.Is<Transition<Thing>>(x => HasSubject(x, thing)));
}

private bool HasSubject(Transition<Thing> x, Thing thing) {
    return x != null && x.Subject != null && x.Subject.Equals(thing));
}

//or...
[Test]
public void Blah2() {
    var thing = new Thing();
    mockRouter.Route(ArgWith(thing));
    // ...
    var result = handler.Handle(thing);
    // ...
    mockRouter.Received(1).Route(ArgWith(thing));
}

private Transition<Thing> ArgWith(Thing thing) {
    return Arg.Is<Transition<Thing>>(x => x != null && x.Subject != null && x.Subject.Equals(thing));
}
Другие вопросы по тегам