Troubleshooting¶
Common Burla failures are usually telling you something useful: a call was stricter than expected, a sequence ran out, or a class member could not be intercepted. This page focuses on the current 1.0.0 behavior.
UnexpectedCallException in strict mode¶
Burla is strict by default. If a call has no matching setup, Burla throws UnexpectedCallException:
var mock = Mock.Of<ICalculator>();
mock.Setup(x => x.Add(1, 1)).Returns(2);
mock.Instance.Add(2, 2); // throws UnexpectedCallException
Typical causes:
- the arguments do not match the setup you wrote
- a void method was never set up
- a property setter was used without
SetupSet(...) - the code under test made one more call than you expected
A few quick fixes:
// Match the exact call
mock.Setup(x => x.Add(2, 2)).Returns(4);
// Or widen the setup with matchers
mock.Setup(x => x.Add(Arg.Any<int>(), Arg.Any<int>())).Returns(0);
// Void methods still need a setup in strict mode
mock.Setup(x => x.Send(Arg.Any<string>()));
If the behavior really should be permissive, switch that mock to loose mode:
Understanding the error message¶
Burla's UnexpectedCallException shows closest matching setups to help you quickly identify what went wrong:
Unexpected call to Calculator.Add(5, 10).
No matching setup found. Closest matches:
- Calculator.Add(1, 2) → Returns 3 (arg[0] mismatch (expected 1, got 5))
- Calculator.Add(10, 20) → Returns 30 (arg[0] mismatch (expected 10, got 5))
- Calculator.Multiply(2, 3) → Returns 6 (method signature mismatch)
Configure a matching setup or use MockBehavior.Loose.
The error message ranks setups by relevance: 1. Same method, wrong arguments - Most likely what you intended 2. Different method, same return type - Possibly a mistake 3. Completely different method - Least likely match
Use this information to: - Check if you made a typo in the method name - Verify your argument matchers are correct - Add the missing setup for the actual call
Return sequence exhausted¶
ReturnsSequence(...) throws SequenceExhaustedException by default after the last configured value is used:
var mock = Mock.Of<ICalculator>();
mock.Setup(x => x.Add(1, 2)).ReturnsSequence(3);
Assert.Equal(3, mock.Instance.Add(1, 2));
Assert.Throws<SequenceExhaustedException>(() => mock.Instance.Add(1, 2));
This is intentional. Burla does not silently return default or repeat the last value unless you ask it to.
Choose the exhaustion behavior you want up front:
mock.Setup(x => x.Add(1, 2)).ReturnsSequence(3, 5).ThenRepeatsLast();
mock.Setup(x => x.Add(1, 2)).ReturnsSequence(3).ThenReturns(9);
mock.Setup(x => x.Add(1, 2)).ReturnsSequence(3).ThenThrows<InvalidOperationException>();
Use this when the code under test may legitimately call the member more times than your "happy path" setup covered.
Sealed classes and non-virtual members¶
Burla can mock interfaces, abstract classes, and concrete classes with virtual members. Two common limitations are worth calling out explicitly.
Sealed classes cannot be mocked¶
var ex = Assert.Throws<ArgumentException>(() => Mock.Of<SealedService>());
Assert.Contains("sealed", ex.Message, StringComparison.OrdinalIgnoreCase);
There is no Burla-specific workaround for this in 1.0.0. Extract an interface, depend on an abstract base class, or wrap the sealed type behind something you can mock.
Non-virtual members are not intercepted¶
On concrete class mocks, non-virtual members run the real implementation and cannot be overridden with Setup(...).
var mock = Mock.Of<ConcreteService>(MockBehavior.Loose);
mock.Setup(x => x.GetStatus()).Returns("mocked"); // virtual member
Assert.Equal("mocked", mock.Instance.GetStatus());
var result = mock.Instance.NonVirtualMethod(); // real implementation
CallBase does not change this limitation. It only affects non-abstract virtual members when no setup matches.
Property setters in strict mode¶
Property getters and setters are configured separately.
A getter setup does not automatically allow assignments:
var mock = Mock.Of<IConfigService>();
mock.Setup(x => x.BaseUrl).Returns("http://test");
Assert.Throws<UnexpectedCallException>(() => mock.Instance.BaseUrl = "http://new-value");
To allow a setter in strict mode, add SetupSet(...):
var mock = Mock.Of<IConfigService>();
mock.Setup(x => x.BaseUrl).Returns("http://test");
mock.SetupSet<string>(x => x.BaseUrl);
mock.Instance.BaseUrl = "http://new-value"; // allowed
If you need to inspect what was assigned, attach a callback:
string? captured = null;
mock.SetupSet<string>(x => x.BaseUrl)
.Callback<string>(value => captured = value);
Note
SetupSet(...) allows the setter call, but it does not create an automatic backing store. If you want the getter to reflect assigned values, capture them yourself and return them from the getter setup.
If the property does not have a public setter, SetupSet(...) throws ArgumentException.
Async stream timing and cancellation pitfalls¶
Burla's ReturnsAsyncEnumerable(...) support is first-class, but a few details matter when tests start failing on timing or cancellation.
WithDelayBetweenItems(...) delays only after an item is yielded¶
The first item is available immediately. The configured delay is applied between items:
mock.Setup(x => x.StreamDataAsync(Arg.Any<CancellationToken>()))
.ReturnsAsyncEnumerable("a", "b", "c")
.WithDelayBetweenItems(TimeSpan.FromMilliseconds(50));
If your test cancels before the second item, seeing the first item still arrive is expected.
Pass the cancellation token through the whole enumeration¶
When testing cancellation, pass the token to the mocked member and to the enumeration:
using var cts = new CancellationTokenSource();
await foreach (var item in mock.Instance.StreamDataAsync(cts.Token)
.WithCancellation(cts.Token))
{
// ...
}
This mirrors the existing samples and makes cancellation intent obvious.
Producer overloads must honor the token¶
If you use the producer overload, the delegate receives a CancellationToken. Use it in your own awaits and checks:
mock.Setup(x => x.StreamDataAsync(Arg.Any<CancellationToken>()))
.ReturnsAsyncEnumerable(async (yield, ct) =>
{
for (int i = 0; i < 5; i++)
{
ct.ThrowIfCancellationRequested();
await yield($"item-{i}");
await Task.Delay(50, ct);
}
});
If you ignore ct, your producer can keep running longer than the test expects.
Disposing the enumerator cancels the producer promptly¶
Burla links disposal to cancellation for producer-based async streams. If the consumer stops early, dispose the enumerator or exit the await foreach cleanly so background work can stop.