API Guide¶
Complete reference for Burla's public API.
Features¶
- Thread-safe: Mocks can be safely used across multiple threads without additional synchronization
- Strict-by-default: Unconfigured calls throw
UnexpectedCallException - LLM-friendly: Clear, explicit API designed for easy automated migration
Mock creation¶
Mock.Of<T>()¶
Creates a strict mock of interface T. Unconfigured calls throw UnexpectedCallException.
Mock.Of<T>(MockBehavior)¶
Creates a mock with specific behavior.
Mock.OfLoose<T>()¶
Creates a loose mock using the shorthand helper:
Mock.Of<T>(params object[] constructorArgs)¶
Creates a mock of a class, passing arguments to its constructor:
If you need to pass a single null constructor argument, use the explicit named-array form:
Mock.Of<T>(MockBehavior, params object[] constructorArgs)¶
Creates a mock of a class with specific behavior and constructor arguments:
Mock.OfLoose<T>(params object[] constructorArgs)¶
Creates a loose class mock with constructor arguments:
Mock.Create<T>()¶
Creates a strict mock and returns the runtime instance directly:
You can also configure the wrapper inline before the instance is returned:
Mock.CreateLoose<T>()¶
Creates a loose mock and returns the runtime instance directly:
For class mocks, pass constructor arguments through the optional constructorArgs: parameter.
var storage = Mock.Create<StorageBase>(
constructorArgs: ["connectionString", 30]);
var configuredStorage = Mock.Create<StorageBase>(
mock =>
{
mock.Setup(x => x.GetData("key")).Returns("value");
},
constructorArgs: ["connectionString"]);
When to use Create... helpers
Use Mock.Create<T>() / Mock.CreateLoose<T>() when you only need the runtime dependency, especially in setup-only or NSubstitute-style tests.
If you need Verify, CallsTo, Reset, RecordedCalls, or CallBase, keep the IMock<T> from Mock.Of<T>() / Mock.OfLoose<T>() and use mock.Instance.
Mocking classes¶
Burla supports mocking interfaces, abstract classes, and concrete classes with virtual methods:
// Abstract class
var mock = Mock.Of<MyAbstractService>();
mock.Setup(x => x.GetData()).Returns("mocked");
// Concrete class with virtual methods
var mock = Mock.Of<ConcreteService>();
mock.Setup(x => x.VirtualMethod()).Returns(42);
// Class with constructor arguments
var mock = Mock.Of<DatabaseRepository>("server=localhost", TimeSpan.FromSeconds(5));
mock.Setup(x => x.QueryAsync(Arg.Any<string>())).ReturnsAsync(new List<Item>());
Limitations:
- Sealed classes cannot be mocked — throws
ArgumentException - Non-virtual methods on concrete classes execute the original implementation and cannot be set up
- Constructor arguments are matched by type to find the best matching constructor
var mock = Mock.Of<ConcreteService>(MockBehavior.Loose);
// Virtual members can be intercepted
mock.Setup(x => x.GetStatus()).Returns("mocked");
Assert.Equal("mocked", mock.Instance.GetStatus());
// Non-virtual members still run the real implementation
// mock.Setup(x => x.NonVirtualMethod()).Returns("override"); // won't be matched
var result = mock.Instance.NonVirtualMethod();
MockBehavior¶
| Value | Behavior |
|---|---|
Strict |
Throws UnexpectedCallException for unconfigured calls (default) |
Loose |
Returns default(T) for unconfigured calls |
The mock instance¶
IMock<T>.Instance¶
Preferred way to get the runtime instance to pass to code under test.
IMock<T>.Object¶
Compatibility alias for Moq-style code. Returns the same runtime instance as .Instance.
IMock<T>.CallBase¶
For class mocks, set CallBase = true to fall back to the base implementation when
no setup matches a non-abstract virtual member:
var mock = Mock.Of<ConcreteService>();
mock.CallBase = true;
Assert.Equal("OK", mock.Instance.GetStatus());
// Explicit setups still win
mock.Setup(x => x.GetStatus()).Returns("overridden");
Assert.Equal("overridden", mock.Instance.GetStatus());
CallBase has no effect on interface mocks and cannot make abstract or non-virtual
members callable.
Setup¶
Methods returning a value¶
ISetup<T, TResult> provides:
| Method | Description |
|---|---|
.Returns(value) |
Return a fixed value |
.Returns(Func<TResult>) |
Compute value on each call |
.Returns<T1>(Func<T1, TResult>) |
Compute value using the method argument |
.Returns<T1, T2>(Func<T1, T2, TResult>) |
Compute value using the method arguments (up to 4 args) |
.ReturnsSequence(params TResult[]) |
Return values from a sequence |
.Throws(Exception) |
Throw a specific exception |
.Throws<TException>() |
Throw by type (creates the exception eagerly) |
.Throws<T1, TException>(Func<T1, TException>) |
Throw with a factory that receives the method argument (up to 4 args) |
Factory returns¶
Factories are evaluated on every matching call:
int counter = 0;
mock.Setup(x => x.GetId()).Returns(() => ++counter);
Assert.Equal(1, mock.Instance.GetId());
Assert.Equal(2, mock.Instance.GetId());
Void methods¶
ISetup<T> provides:
| Method | Description |
|---|---|
.Callback(Action) |
Execute an action when called |
.Callback<T1>(Action<T1>) |
Execute a typed callback that receives the method argument |
.Callback<T1, T2>(Action<T1, T2>) |
Typed callback with method arguments (up to 4 args) |
.Throws(Exception) |
Throw a specific exception |
.Throws<TException>() |
Throw by type (creates the exception eagerly) |
.Throws<T1, TException>(Func<T1, TException>) |
Throw with a factory that receives the method argument (up to 4 args) |
.SetsByRefParameter<TOut>(int index, TOut value) |
Set an out/ref parameter value |
Properties¶
Properties use the same Setup method — pass the property getter:
For low-diff Moq migrations, SetupGet(...) is also available as a compatibility alias for property getters:
Burla-native docs still prefer Setup(...) for both methods and property getters.
To allow property setters in strict mode, use SetupSet:
var mock = Mock.Of<IConfigService>();
mock.Setup(x => x.BaseUrl).Returns("https://example.com");
mock.SetupSet<string>(x => x.BaseUrl);
mock.Instance.BaseUrl = "https://staging.example.com"; // allowed
You can also attach a typed callback to capture the assigned value:
string? captured = null;
mock.SetupSet<string>(x => x.BaseUrl)
.Callback<string>(value => captured = value);
Last-match-wins¶
When multiple setups match a call, the last matching setup wins. This lets you set up general behavior first and override for specific cases:
mock.Setup(x => x.Add(Arg.Any<int>(), Arg.Any<int>())).Returns(0); // general
mock.Setup(x => x.Add(1, 1)).Returns(2); // specific
mock.Setup(x => x.Add(Arg.Is<int>(n => n > 100), Arg.Any<int>())).Returns(999);
mock.Instance.Add(1, 1); // 2 (second setup matches)
mock.Instance.Add(5, 5); // 0 (first setup matches)
mock.Instance.Add(200, 1); // 999 (third setup matches)
Typed Returns (argument forwarding)¶
Pass method arguments to a factory function:
// Single argument
mock.Setup(x => x.GetDataAsync(Arg.Any<int>()))
.Returns<int>(id => Task.FromResult($"data-{id}"));
// Two arguments
mock.Setup(x => x.Add(Arg.Any<int>(), Arg.Any<int>()))
.Returns<int, int>((a, b) => a + b);
Typed overloads go up to 4 arguments: Returns<T1>, Returns<T1, T2>, Returns<T1, T2, T3>, Returns<T1, T2, T3, T4>.
Factory-based Throws¶
Throw exceptions that depend on the arguments passed to the method:
// Exception factory receives method arguments
mock.Setup(x => x.Divide(Arg.Any<int>(), Arg.Any<int>()))
.Throws<int, int, InvalidOperationException>((a, b) =>
new InvalidOperationException($"Cannot divide {a} by {b}"));
// Single argument
mock.Setup(x => x.Send(Arg.Any<string>()))
.Throws<string, ArgumentException>(msg =>
new ArgumentException($"Invalid message: {msg}"));
Factory overloads go up to 4 arguments. Available on both ISetup<T, TResult> (return methods) and ISetup<T> (void methods).
Post-return chaining¶
After .Returns(...), you can chain:
.Callback(Action) / .Callback<T1, ...>(Action<T1, ...>)¶
Execute code after returning. Typed overloads receive the method arguments:
int callCount = 0;
mock.Setup(x => x.Add(1, 1)).Returns(2).Callback(() => callCount++);
// Typed: captures the actual arguments
int capturedA = 0, capturedB = 0;
mock.Setup(x => x.Add(Arg.Any<int>(), Arg.Any<int>()))
.Returns(0)
.Callback<int, int>((a, b) => { capturedA = a; capturedB = b; });
.SetsByRefParameter<TOut>(int parameterIndex, TOut value)¶
Set an out/ref parameter on a method that also returns a value:
Sequences¶
Return different values on consecutive calls:
ISequenceResult<T, TResult> controls what happens after exhaustion:
| Method | Behavior after values are used |
|---|---|
.ThenRepeatsLast() |
Repeat the last value (opt-in) |
.ThenReturns(value) |
Return a specific value |
.ThenThrows(Exception) |
Throw a specific exception |
.ThenThrows<TException>() |
Throw by type |
| (default) | Throw SequenceExhaustedException |
// After 10 and 20, return 0 forever
mock.Setup(x => x.Add(1, 1)).ReturnsSequence(10, 20).ThenReturns(0);
// After 10 and 20, throw
mock.Setup(x => x.Add(1, 1)).ReturnsSequence(10, 20).ThenThrows<InvalidOperationException>();
Argument matchers¶
Used in Setup and CallsTo/Verify expressions. Burla-native docs use Arg; It exposes the same matcher surface as low-cost Moq compatibility sugar.
Arg.Any<T>()¶
Matches any value of type T:
Arg.Is<T>(Func<T, bool>)¶
Matches values satisfying a predicate:
Arg.IsIn<T>(params T[])¶
Matches values in the specified set:
Arg.IsNotIn<T>(params T[])¶
Matches values not in the specified set:
Arg.IsNull<T>()¶
Matches a null reference:
Arg.IsNotNull<T>()¶
Matches any non-null reference:
Arg.Ref<T>.Any¶
Matches any ref or out parameter. Use with ref or out keyword in the expression:
mock.Setup(x => x.TryParse("42", out Arg.Ref<int>.Any))
.Returns(true)
.SetsByRefParameter(1, 42);
mock.Setup(x => x.Transform(ref Arg.Ref<string>.Any))
.SetsByRefParameter(0, "TRANSFORMED");
It¶
Moq-compatible alias for Arg. Useful when you want migration diffs to stay small:
Exact values¶
Pass values directly — they're matched with object.Equals:
Ref/Out parameters¶
Out parameters¶
Use out Arg.Ref<T>.Any in the setup expression and .SetsByRefParameter(index, value) to configure the output:
var mock = Mock.Of<IParser>();
mock.Setup(x => x.TryParse("42", out Arg.Ref<int>.Any))
.Returns(true)
.SetsByRefParameter(1, 42);
mock.Setup(x => x.TryParse("abc", out Arg.Ref<int>.Any))
.Returns(false)
.SetsByRefParameter(1, 0);
mock.Instance.TryParse("42", out var v1); // true, v1 = 42
mock.Instance.TryParse("abc", out var v2); // false, v2 = 0
Ref parameters¶
Use ref Arg.Ref<T>.Any to match and .SetsByRefParameter(index, value) to set the new value:
mock.Setup(x => x.Transform(ref Arg.Ref<string>.Any))
.SetsByRefParameter(0, "TRANSFORMED");
var text = "hello";
mock.Instance.Transform(ref text); // text is now "TRANSFORMED"
Async methods¶
ReturnsAsync¶
Shorthand for Returns(Task.FromResult(value)) — but you can also just use Returns directly:
// Smart inference — auto-wraps in Task.FromResult:
mock.Setup(x => x.GetDataAsync(1)).Returns("data-1");
// Explicit ReturnsAsync (same result):
mock.Setup(x => x.GetDataAsync(1)).ReturnsAsync("data-1");
ValueTask¶
Same smart inference works for ValueTask<T>:
mock.Setup(x => x.GetCountAsync()).Returns(42);
// Or explicitly:
mock.Setup(x => x.GetCountAsync()).ReturnsAsync(42);
mock.Setup(x => x.GetCountAsync()).Returns(new ValueTask<int>(42));
IAsyncEnumerable¶
Simple items¶
mock.Setup(x => x.StreamDataAsync(Arg.Any<CancellationToken>()))
.ReturnsAsyncEnumerable("item1", "item2", "item3");
Custom yield logic¶
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}");
}
});
Delay between items¶
Useful for testing timeout and cancellation scenarios:
mock.Setup(x => x.StreamDataAsync(Arg.Any<CancellationToken>()))
.ReturnsAsyncEnumerable("a", "b", "c")
.WithDelayBetweenItems(TimeSpan.FromMilliseconds(100));
Verification¶
CallsTo (recommended)¶
Returns recorded calls matching an expression. Use with standard assertions:
var calls = mock.CallsTo(x => x.Add(Arg.Any<int>(), Arg.Any<int>()));
Assert.Equal(3, calls.Count);
var specificCalls = mock.CallsTo(x => x.Send("hello"));
Assert.Single(specificCalls);
Works with void methods too:
For everyday Burla tests, prefer CallsTo(...) + normal test assertions. Verify(...) and Times remain available as optional helpers, mainly for low-diff Moq migrations or for VerifyNoOtherCalls().
Verify (optional helper)¶
If you want a helper that throws on mismatch, use Verify(...) with a Times constraint:
mock.Verify(x => x.Send("hello"), Times.Once());
mock.Verify(x => x.Send("error"), Times.Never());
mock.Verify(x => x.GetDataAsync(Arg.Any<int>()), Times.AtLeast(2));
Most Burla-native tests can instead assert on CallsTo(...).Count or use helpers such as Assert.Single(...) and Assert.Empty(...).
VerifyNoOtherCalls()¶
Verifies that no calls were made beyond those already verified via Verify.
Throws VerificationException if unverified calls exist:
mock.Instance.Send("hello");
mock.Instance.SendToUser("user1", "world");
mock.Verify(x => x.Send("hello"), Times.Once());
mock.VerifyNoOtherCalls(); // 💥 throws — SendToUser was not verified
Note
CallsTo(...) does not mark calls as verified. Only Verify(...) does.
Use Verify before VerifyNoOtherCalls to mark expected calls.
Mock.Sequence() / .InSequence(sequence)¶
Use ordered setups when call order matters across one or more mocks.
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:
InSequence(sequence)registers the setup as the next expected ordered step.- An out-of-order matching call throws
VerificationExceptionimmediately. - Disposing the sequence verifies that every registered step was observed.
- A single sequence can coordinate setups across multiple mocks.
Reset()¶
Clears mutable mock state: setups, recorded calls, verification tracking, and event subscriptions captured by the mock. Reset() does not recreate the mock object or change other configuration such as CallBase.
var partial = Mock.Of<ConcreteService>();
partial.CallBase = true;
partial.Setup(x => x.VirtualMethod()).Returns(42);
Assert.Equal(42, partial.Instance.VirtualMethod());
partial.Reset();
// All state is gone:
Assert.Empty(partial.RecordedCalls);
// Other configuration is preserved
Assert.True(partial.CallBase);
// With no setup left, class mocks still follow their remaining configuration.
// Here, CallBase stays enabled for later calls to non-abstract virtual members.
// New setups work normally
partial.Setup(x => x.VirtualMethod()).Returns(99);
Assert.Equal(99, partial.Instance.VirtualMethod());
RecordedCalls¶
Access all recorded calls for advanced inspection:
var allCalls = mock.RecordedCalls;
var sendCalls = allCalls
.Where(c => c.Method.Name == "SendToUser")
.ToList();
Assert.Equal("user1", sendCalls[0].GetArgument<string>(0));
Assert.Equal("Hello", sendCalls[0].GetArgument<string>(1));
Times¶
Used with Verify when you want Moq-style count constraints. Most Burla-native tests can just query calls and use standard assertions.
| Factory method | Meaning |
|---|---|
Times.Never() |
Must not be called |
Times.Once() |
Exactly 1 call |
Times.AtLeastOnce() |
1 or more calls |
Times.AtLeast(n) |
At least n calls |
Times.AtMost(n) |
At most n calls |
Times.Exactly(n) |
Exactly n calls |
Times.Between(from, to) |
Between from and to calls (inclusive) |
CallRecord¶
Each recorded invocation is a CallRecord with:
| Property | Type | Description |
|---|---|---|
Method |
MethodInfo |
The method that was called |
Arguments |
object?[] |
The arguments that were passed |
ReturnValue |
object? |
The value that was returned |
Timestamp |
DateTime |
When the call was recorded (UTC) |
GetArgument<T>(int index)¶
Typed access to a specific argument:
var call = mock.RecordedCalls.Single();
var name = call.GetArgument<string>(0);
var count = call.GetArgument<int>(1);
Exceptions¶
UnexpectedCallException¶
Thrown in strict mode when a call has no matching setup.
Properties:
Method— theMethodInfothat was calledArguments— the arguments passedClosestMatches— read-only list ofSetupInfoentries describing the closest matching setups (ranked by relevance). Empty when Burla has no useful suggestions.
Message format (with closest matches):
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.
Message format (no matching setups):
Closest matches are ranked by relevance: 1. Same method with argument mismatches (highest priority) 2. Different methods with same return type 3. Completely different methods (lowest priority)
VerificationException¶
Thrown by Verify when the call count doesn't match.
Message format:
SequenceExhaustedException¶
Thrown when a return sequence is exhausted and no exhaustion behavior has been configured.
Properties: - None (standard exception with message only)
Message format:
Return sequence exhausted. The sequence has no more values to return. Configure exhaustion behavior using ThenRepeatsLast(), ThenReturns(value), or ThenThrows(exception).
Constructors:
- SequenceExhaustedException() — default message
- SequenceExhaustedException(string message) — custom message
Event Emission¶
Event(string eventName).Emit(params object[] args)¶
Burla's native event API selects an event by name and then emits it to the current subscribers. The first argument is the event sender (typically mock.Instance), followed by the event arguments.
Contract:
- Unknown event names throw
ArgumentException. - Known events with no subscribers are a no-op.
- The argument count must exactly match the event handler delegate signature.
- Delegate shapes are not restricted to a special small set; any event delegate arity is supported.
var mock = Mock.Of<IEventPublisher>(MockBehavior.Loose);
string? receivedMessage = null;
mock.Instance.MessageReceived += (sender, msg) => receivedMessage = msg;
mock.Event(nameof(IEventPublisher.MessageReceived)).Emit(mock.Instance, "Hello!");
// receivedMessage is now "Hello!"
For events that use EventArgs, pass an instance of the event args: