Skip to main content Link Search Menu Expand Document (external link)

Recording and Verifying

Table of contents

DivertR can record the details of calls to its proxies and this can be used for test spying and verification.

Record

The Redirect fluent interface is used to start a recording of proxy calls that match a To expression:

var fooRedirect = new Redirect<IFoo>();
var fooProxy = fooRedirect.Proxy(new Foo());

var echoCalls = fooRedirect
    .To(x => x.Echo(Is<string>.Any)) // Call match expression
    .Record(); // Returns an ICallStream collection of all recorded calls

var result = fooProxy.Echo("record test");
Console.WriteLine(result); // "record test"

// ICallStream is an `IReadOnlyCollection`
Console.WriteLine(echoCalls.Count); // 1

for(var call in echoCalls)
{
    Console.WriteLine(call.Args[0]); // "record test"
    Console.WriteLine(call.Returned.Value); // "record test"
}

The Record method returns an ICallStream that recorded called are appended to. This variable is an IReadOnlyCollection and IEnumerable that can be enumerated or queried e.g. with standard Linq expressions.

Verify snapshot

The ICallStream interface provides Verify helper methods to facilitate iteration and verification over the recorded calls collection.

Recorded calls are appended to the ICallStream whenever a matching proxy call is made. This means the ICallStream will hold different record data at different points in time as calls are happening.

Therefore the Verify method iterates over and returns an immutable snapshot of the collection of recorded calls at the point of time it is called. This allows performing multiple, consistent operations on a stable set of data. E.g. in the example above iterating over the collection and then verifying the call count.

var echoCalls = fooRedirect
    .To(x => x.Echo(Is<string>.Any))
    .Record();

fooProxy.Echo("one");
Console.WriteLine(echoCalls.Count); // 1

// Create verify snapshot
var verifyCalls = echoCalls.Verify();

// Call recorded method again
fooProxy.Echo("two");

// Record call stream has both calls
Console.WriteLine(echoCalls.Count); // 2

// Snapshot is immutable 
Console.WriteLine(verifyCalls.Count); // 1
Console.WriteLine(verifyCalls[0].Args[0]); // one

Verify Visitor

The ICallStream interface provides Verify helper methods to facilitate iteration and verification over the recorded calls collection.

var fooRedirect = new Redirect<IFoo>();
var fooProxy = fooRedirect.Proxy(new Foo());

var nameCalls = fooRedirect
    .To(x => x.Echo(Is<string>.Any)) 
    .Record();

var result = fooProxy.Echo("record");

var verifySnapshot = nameCalls.Verify(call =>
{
    call.Args[0].ShouldBe("record");
    call.Returned.Value.ShouldBe("record");
});

verifySnapshot.Count.ShouldBe(1); // The verify snapshot records calls at a point in time and is immutable

Record chaining

The Redirect fluent interface allows chaining the Record method after a Via call:

var nameCalls = fooRedirect
    .To(x => x.Echo(Is<string>.Any))
    .Via(call => call.CallNext() + " redirected")
    .Record();

var result = fooProxy.Echo("record");

nameCalls.Verify(call =>
{
    call.Args[0].ShouldBe("record");
    call.Returned.Value.ShouldBe("record redirected");
}).Count.ShouldBe(1);

Named Arguments

The Verify methods allows specifying call argument types and names using the same Via ValueTuple syntax:

var nameCalls = fooRedirect
    .To(x => x.Echo(Is<string>.Any))
    .Record();

var result = fooProxy.Echo("record example");

nameCalls.Verify<(string input, __)>(call =>
{
    call.Args.input.ShouldBe("record example");
    call.Returned.Value.ShouldBe("record example echo");
}).Count.ShouldBe(1);

The argument ValueTuple can also be defined on the Record method and gets passed through to Verify calls:

var nameCalls = fooRedirect
    .To(x => x.Echo(Is<string>.Any))
    .Record<(string input, __)>();

var result = fooProxy.Echo("record example");

nameCalls.Verify(call =>
{
    call.Args.input.ShouldBe("record example");
    call.Returned.Value.ShouldBe("record example echo");
}).Count.ShouldBe(1);

Finally if the argument ValueTuple is defined on a chained Via the strongly typed argument information is passed through to the Record and can be used in the Verify calls:

var nameCalls = fooRedirect
    .To(x => x.Echo(Is<string>.Any))
    .Via<(string input, __)>(call => $"{call.Args.input} redirected")
    .Record();

var result = fooProxy.Echo("record example");

nameCalls.Verify(call =>
{
    call.Args.input.ShouldBe("record example");
    call.Returned.Value.ShouldBe("record example redirected");
}).Count.ShouldBe(1);

Recording exceptions

var nameCalls = fooRedirect
    .To(x => x.Echo(Is<string>.Any))
    .Via<(string input, __)>(() => throw new Exception())
    .Record();

Exception caughtException = null;
try
{
    fooProxy.Echo("record example");
}
catch (Exception ex)
{
    caughtException = ex;
}

nameCalls.Verify(call =>
{
    call.Args.input.ShouldBe("record example");
    call.Returned.Exception.ShouldBe(caughtException)
    call.Returned.Value.ShouldBeNull();
}).Count.ShouldBe(1);

Redirect Record

var fooRedirect = new Redirect<IFoo>();
var fooProxy = fooRedirect.Proxy(new Foo());

// Record all Redirect proxy calls
var fooCalls = fooRedirect.Record();

fooProxy.Echo("record");

fooCalls
    .To(x => x.Echo(Is<string>.Any)) // Use the 'To' expression to filter Redirect recorded calls 
    .Verify<(string input, __)>(call =>
    {
        call.Args.input.ShouldBe("record");
        call.Returned.Value.ShouldBe("record");
    }).Count.ShouldBe(1);

Record ordering

var fooRedirect = new Redirect<IFoo>();
var fooProxy = fooRedirect.Proxy(new Foo());

var fooCalls = fooRedirect.Record(opt => opt.OrderFirst());

fooRedirect
    .To(x => x.Echo(Is<string>.Any))
    .Via<(string input, __)>(call => call.CallNext() + " redirected")

fooProxy.Echo("record");

fooCalls
    .To(x => x.Echo(Is<string>.Any))
    .Verify<(string input, __)>(call =>
    {
        call.Args.input.ShouldBe("record");
        call.Returned.Value.ShouldBe("record redirected");
    }).Count.ShouldBe(1);