Advanced Patterns¶
Once the basics are in place, Burla gives you a few sharp tools for inspecting calls and tightening behavior without switching to a different API style.
Inspecting RecordedCalls¶
CallsTo(...) is the usual query-and-assert path, but RecordedCalls exposes every invocation when you need more detail.
var mock = Mock.Of<INotificationService>();
mock.Setup(x => x.SendToUser(Arg.Any<string>(), Arg.Any<string>()));
mock.Instance.SendToUser("user1", "Hello");
var call = mock.RecordedCalls.Single();
Assert.Equal("SendToUser", call.Method.Name);
Assert.Equal("user1", call.GetArgument<string>(0));
Assert.Equal("Hello", call.GetArgument<string>(1));
Each CallRecord includes:
MethodArgumentsReturnValueTimestamp(UTC)
Use RecordedCalls when you need custom filtering, ordering checks, or argument inspection that does not map cleanly to a single CallsTo(...) query.
Ordered verification with Mock.Sequence()¶
Most verification in Burla is order-agnostic. When order matters, create a sequence and register setups with InSequence(...).
using var sequence = Mock.Sequence();
var notifications = Mock.Of<INotificationService>();
var audit = Mock.Of<IAuditSink>();
notifications.Setup(x => x.Send("starting")).InSequence(sequence);
audit.Setup(x => x.Write("started")).InSequence(sequence);
notifications.Instance.Send("starting");
audit.Instance.Write("started");
Contract:
- an out-of-order matching call throws
VerificationExceptionimmediately - disposing the sequence verifies that all registered steps were observed
- one sequence can coordinate calls across multiple mocks
- repeated identical steps are matched in registration order
This is the right fit when the sequence itself is part of the behavior you care about.
Last-match-wins overrides¶
Burla resolves matching setups from newest to oldest. That means the last matching setup wins.
var mock = Mock.Of<ICalculator>();
mock.Setup(x => x.Add(Arg.Any<int>(), Arg.Any<int>())).Returns(0);
mock.Setup(x => x.Add(1, 1)).Returns(2);
mock.Setup(x => x.Add(Arg.Is<int>(n => n > 100), Arg.Any<int>())).Returns(999);
This makes it easy to layer behavior:
- start with a broad fallback
- add specific overrides later
- keep the most specific or most recent rule closest to the test that needs it
Assert.Equal(2, mock.Instance.Add(1, 1));
Assert.Equal(0, mock.Instance.Add(5, 5));
Assert.Equal(999, mock.Instance.Add(200, 1));
Post-return callbacks¶
For members that return a value, callbacks run after the return value has been chosen.
var mock = Mock.Of<ICalculator>();
int capturedA = 0, capturedB = 0;
mock.Setup(x => x.Add(Arg.Any<int>(), Arg.Any<int>()))
.Returns<int, int>((a, b) => a * b)
.Callback<int, int>((a, b) => { capturedA = a; capturedB = b; });
var result = mock.Instance.Add(3, 7);
Assert.Equal(21, result);
Assert.Equal(3, capturedA);
Assert.Equal(7, capturedB);
Use this when you want both:
- a computed or fixed return value
- side effects such as logging, capture, or extra assertions
For void members, use Callback(...) directly on the setup with no Returns(...) step.
Factory returns¶
Factories are evaluated on each matching call, so they are ideal when the return value depends on arguments or call count.
Per-call state¶
var mock = Mock.Of<ICalculator>();
int counter = 0;
mock.Setup(x => x.GetId()).Returns(() => ++counter);
Argument forwarding¶
var mock = Mock.Of<IAsyncDataService>(MockBehavior.Loose);
mock.Setup(x => x.GetDataAsync(Arg.Any<int>()))
.Returns<int>(id => Task.FromResult($"data-{id}"));
Typed overloads go up to four arguments, so you can keep the setup close to the method signature instead of introducing separate helper delegates.
Setting out and ref values with SetsByRefParameter(...)¶
Burla uses Arg.Ref<T>.Any in the setup expression and SetsByRefParameter(...) to update out or ref parameters.
out parameters¶
var mock = Mock.Of<IParser>();
mock.Setup(x => x.TryParse("42", out Arg.Ref<int>.Any))
.Returns(true)
.SetsByRefParameter(1, 42);
ref parameters¶
A few rules to keep in mind:
- the index is zero-based and refers to the parameter position
- combine
SetsByRefParameter(...)withReturns(...)when the method also has a return value - multiple setups work well for different input/output combinations