Is there a way to cache an Arg.Is<> definition for use in both the "Arrange" and "Act" parts of a test?

154 Views Asked by At

I have a test that looks like this:

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

I would like to cache up the Arg definition in a local variable so I can reuse it in the assert. The point is to reduce the amount of code in the test and make it read a bit more fluidly.

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

This does not seem to work, as the value of transitionForThing is null, and so the assertion fails saying that Received(null) was not called. Is there a way to do this or something similar, or am I stuck with this syntax?

2

There are 2 best solutions below

1
On BEST ANSWER

Arg.Is has a parameter of type

Expression<Predicate<T>>

, so you can define it to reuse

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

and use it as

mockRouter.Route(predicate);

But i really don't understand your situation: you usually mock classes that return some result that you need. I think in your case you only need to check that the method of the mocked class has been called, you don't need to define a mock for the action.

0
On

The value returned by Arg.Is(...) and other Arg methods is of relatively little importance. The important bit is the invocation of the method itself.

Every time Arg.Is(...) is called NSubstitute takes note that you tried to specify an argument, and uses that information to work out the details of the call you are specifying. If you cache the value (which will just be default(T)), NSubstitute will not know you want to match the same argument.

I can explain this more if you are interested, but the important thing to note is you need to call Arg.Is(...) every time you specify a call (and in the correct order expected for the call).

To reuse matcher logic I'd extract the predicate into a new method (as DaniCE suggested), or create a method that calls Arg.Is and use it with caution.

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