Skip to content

Migrating from Moq

Side-by-side comparison of Moq and Burla patterns. Every example shows the Moq code you have today and the Burla equivalent.

Package change

- <PackageReference Include="Moq" Version="..." />
+ <PackageReference Include="Burla" Version="..." />
- using Moq;
+ using Burla;

Creating mocks

var mock = new Mock<ICalculator>();           // loose by default
var mock = new Mock<ICalculator>(MockBehavior.Strict);
var mock = Mock.Of<ICalculator>();                     // strict by default
var mock = Mock.Of<ICalculator>(MockBehavior.Loose);

Default behavior change

Moq defaults to loose (unconfigured calls return default). Burla defaults to strict (unconfigured calls throw). If you're migrating a large codebase that relies on loose behavior, pass MockBehavior.Loose explicitly.

Getting the mock instance

Burla keeps .Object as a compatibility alias, so many Moq tests can keep this spelling unchanged during migration. In Burla-native docs and new examples you will usually see .Instance instead.

var calculator = mock.Object;

Quick fixes are IDE-only and optional

The default Burla package includes the BURLA01x compatibility diagnostics for .Object to .Instance, It.* to Arg.*, and SetupGet(...) to Setup(...), plus BURLA020 for forgotten zero-argument Times parentheses inside Verify(...). In supported IDEs these show up as gentle info diagnostics and code fixes, and they do not block compilation.

If you want a quieter migration while you are still porting a large Moq codebase, lower the compatibility rules in .editorconfig:

dotnet_diagnostic.BURLA010.severity = silent
dotnet_diagnostic.BURLA011.severity = silent
dotnet_diagnostic.BURLA012.severity = silent

Use none instead if you want them fully off during migration, then restore them to info later if you want IDE nudges toward the native Burla spellings.

If you want the runtime without those suggestions, install Burla.Core instead. If you want to keep Burla but turn the suggestions off, suppress the Burla diagnostic IDs in .editorconfig:

dotnet_diagnostic.BURLA010.severity = none
dotnet_diagnostic.BURLA011.severity = none
dotnet_diagnostic.BURLA012.severity = none

Simple setup and returns

mock.Setup(x => x.Add(2, 3)).Returns(5);
mock.Setup(x => x.Add(2, 3)).Returns(5);

Identical syntax!

Argument matchers

Moq Burla
It.IsAny<int>() It.IsAny<int>() (compat) / Arg.Any<int>() (native)
It.Is<int>(n => n > 0) It.Is<int>(n => n > 0) (compat) / Arg.Is<int>(n => n > 0)
It.IsIn("a", "b") It.IsIn("a", "b") (compat) / Arg.IsIn("a", "b")
It.IsNotIn("a", "b") It.IsNotIn("a", "b") (compat) / Arg.IsNotIn("a", "b")
It.IsNotNull<string>() It.IsNotNull<string>() (compat) / Arg.IsNotNull<string>()
It.Ref<int>.IsAny It.Ref<int>.IsAny (compat) / Arg.Ref<int>.Any
mock.Setup(x => x.Add(Arg.Any<int>(), Arg.Any<int>())).Returns(42);
mock.Setup(x => x.Add(Arg.Is<int>(n => n > 0), Arg.Any<int>())).Returns(999);
mock.Setup(x => x.Add(Arg.Any<int>(), Arg.Any<int>())).Returns(42);
mock.Setup(x => x.Add(Arg.Is<int>(n => n > 0), Arg.Any<int>())).Returns(999);

Burla-native docs use Arg, but It is available as compatibility sugar when you want a Moq migration to stay low-diff.

Void methods

mock.Setup(x => x.Send("error")).Throws<InvalidOperationException>();

string? captured = null;
mock.Setup(x => x.Send(It.IsAny<string>()))
    .Callback<string>(msg => captured = msg);
mock.Setup(x => x.Send("error")).Throws<InvalidOperationException>();

string? captured = null;
mock.Setup(x => x.Send(Arg.Any<string>()))
    .Callback<string>(msg => captured = msg);

Typed callback overloads (Callback<T1> through Callback<T1,T2,T3,T4>) work the same way as Moq.

Exceptions

mock.Setup(x => x.Divide(It.IsAny<int>(), 0))
    .Throws(new DivideByZeroException("oops"));
mock.Setup(x => x.Send("error")).Throws<InvalidOperationException>();
mock.Setup(x => x.Divide(Arg.Any<int>(), 0))
    .Throws(new DivideByZeroException("oops"));
mock.Setup(x => x.Send("error")).Throws<InvalidOperationException>();

Identical syntax.

Properties

mock.Setup(x => x.BaseUrl).Returns("https://example.com");
mock.Setup(x => x.Timeout).Returns(30);
mock.Setup(x => x.BaseUrl).Returns("https://example.com");
mock.Setup(x => x.Timeout).Returns(30);

Identical.

For low-diff Moq migrations, Burla also accepts SetupGet(...) as a compatibility alias for getter setup:

mock.SetupGet(x => x.BaseUrl).Returns("https://example.com");

SetupAllProperties is not available in Burla

Moq's SetupAllProperties() auto-tracks property changes. In Burla, set up each property individually.

Property setters (SetupSet)

string? captured = null;

mock.SetupSet(x => x.BaseUrl = It.IsAny<string>())
    .Callback<string>(value => captured = value);
string? captured = null;

mock.SetupSet<string>(x => x.BaseUrl)
    .Callback<string>(value => captured = value);

Burla's SetupSet(...) targets the property, not an assignment expression. It allows any assigned value by default and is the supported way to permit setter calls in strict mode.

Sequences

mock.SetupSequence(x => x.Add(1, 1))
    .Returns(10)
    .Returns(20)
    .Returns(30);
// After exhaustion: returns default(int) = 0
mock.Setup(x => x.Add(1, 1)).ReturnsSequence(10, 20, 30);
// After exhaustion: throws SequenceExhaustedException by default

Exhaustion behavior differs

Moq returns default(T) after the sequence is exhausted. Burla throws an exception by default. Use .ThenReturns(defaultValue) to match Moq's behavior explicitly, or .ThenRepeatsLast() if you want repeat-last semantics instead.

Verification

mock.Verify(x => x.Send("test"), Times.Once());
mock.Verify(x => x.Send(It.IsAny<string>()), Times.Never());
mock.Verify(x => x.Send(It.IsAny<string>()), Times.Exactly(3));
// Option A: query and assert (recommended)
Assert.Single(mock.CallsTo(x => x.Send("test")));
Assert.Empty(mock.CallsTo(x => x.Send(Arg.Any<string>())));
Assert.Equal(3, mock.CallsTo(x => x.Send(Arg.Any<string>())).Count);

// Option B: low-diff migration helper (throws on mismatch, like Moq)
mock.Verify(x => x.Send("test"), Times.Once());
mock.Verify(x => x.Send(Arg.Any<string>()), Times.Never());
mock.Verify(x => x.Send(Arg.Any<string>()), Times.Exactly(3));

If you keep low-diff Verify(...) calls during migration, the Times API is the same: Never(), Once(), AtLeastOnce(), AtLeast(n), AtMost(n), Exactly(n), Between(from, to). Once you are comfortable with Burla, most new code can stop at CallsTo(...) + standard assertions.

VerifyNoOtherCalls

mock.Verify(x => x.Send("hello"), Times.Once());
mock.VerifyNoOtherCalls();
mock.Verify(x => x.Send("hello"), Times.Once());
mock.VerifyNoOtherCalls();

Identical syntax! Both throw if unverified calls exist.

Note

As in Burla's other docs, only Verify(...) marks calls as verified for VerifyNoOtherCalls(). CallsTo(...) is query-only.

Verifiable() / VerifyAll()

Burla intentionally keeps verification explicit. Translate Moq's aggregate verification pattern into normal assertions in the Assert phase.

mock.Setup(x => x.Send("hello")).Verifiable();
mock.Setup(x => x.Send("world")); // not marked

mock.Object.Send("hello");
mock.VerifyAll();
mock.Object.Send("hello");
mock.Verify(x => x.Send("hello"), Times.Once());
// or: Assert.Single(mock.CallsTo(x => x.Send("hello")));

Async methods

mock.Setup(x => x.GetDataAsync(1)).ReturnsAsync("data");
mock.Setup(x => x.GetCountAsync()).ReturnsAsync(42);  // ValueTask
mock.Setup(x => x.GetDataAsync(1)).Returns("data");           // smart auto-wrap
mock.Setup(x => x.GetDataAsync(1)).ReturnsAsync("data");      // explicit (same)
mock.Setup(x => x.GetCountAsync()).Returns(42);                // ValueTask too!
mock.Setup(x => x.GetCountAsync()).ReturnsAsync(42);           // explicit (same)

Burla's .Returns(value) auto-wraps into Task.FromResult or ValueTask when the method is async. ReturnsAsync also works if you prefer being explicit.

IAsyncEnumerable

// Requires a helper method
mock.Setup(x => x.StreamDataAsync(It.IsAny<CancellationToken>()))
    .Returns(GetTestDataAsync);

static async IAsyncEnumerable<string> GetTestDataAsync()
{
    yield return "item1";
    await Task.Yield();
    yield return "item2";
    yield return "item3";
}
// First-class support — no helper needed!
mock.Setup(x => x.StreamDataAsync(Arg.Any<CancellationToken>()))
    .ReturnsAsyncEnumerable("item1", "item2", "item3");

Ref/Out parameters

// Verbose: requires a delegate
mock.Setup(x => x.TryParse("42", out It.Ref<int>.IsAny))
    .Returns(new TryParseDelegate((string _, out int result) =>
    {
        result = 42;
        return true;
    }));

delegate bool TryParseDelegate(string input, out int result);

// Ref: also needs a delegate
mock.Setup(x => x.Transform(ref It.Ref<string>.IsAny))
    .Callback(new TransformDelegate((ref string value) =>
    {
        value = value.ToUpper();
    }));

delegate void TransformDelegate(ref string value);
// Clean — no delegates
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");

Callback after return

mock.Setup(x => x.Add(1, 1)).Returns(2).Callback(() => callCount++);
mock.Setup(x => x.Add(It.IsAny<int>(), It.IsAny<int>()))
    .Returns(0)
    .Callback<int, int>((a, b) => { capturedA = a; capturedB = b; });
mock.Setup(x => x.Add(1, 1)).Returns(2).Callback(() => callCount++);
mock.Setup(x => x.Add(Arg.Any<int>(), Arg.Any<int>()))
    .Returns(0)
    .Callback<int, int>((a, b) => { capturedA = a; capturedB = b; });

Identical syntax — typed Callback<T1..T4> overloads work the same way.

Call inspection

// Moq doesn't expose recorded calls directly.
// You use Verify or Callback to capture.
var captured = new List<string>();
mock.Setup(x => x.Send(It.IsAny<string>()))
    .Callback<string>(msg => captured.Add(msg));
// All calls are recorded automatically
mock.Object.Send("hello");

var calls = mock.RecordedCalls;
var arg = calls[0].GetArgument<string>(0); // "hello"

Class mocking

// Abstract class
var mock = new Mock<MyAbstractService>();
mock.Setup(x => x.GetData()).Returns("mocked");

// Concrete class with constructor args
var mock = new Mock<ConcreteService>("arg1", 42);
mock.Setup(x => x.VirtualMethod()).Returns(99);
// Abstract class
var mock = Mock.Of<MyAbstractService>(MockBehavior.Loose);
mock.Setup(x => x.GetData()).Returns("mocked");

// Concrete class with constructor args
var mock = Mock.Of<ConcreteService>(MockBehavior.Loose, "arg1", 42);
mock.Setup(x => x.VirtualMethod()).Returns(99);

Use Mock.OfLoose<T>() helper

For class mock migration, use Mock.OfLoose<T>() as a shorthand for Mock.Of<T>(MockBehavior.Loose). This makes the migration from Moq's default loose behavior more concise.

Behavior difference

Moq defaults to loose mode for class mocks. Burla defaults to strict. Add MockBehavior.Loose explicitly when migrating class mocks that rely on default values.

Partial mocks (CallBase)

var mock = new Mock<ConcreteService>();
mock.CallBase = true;

var status = mock.Object.GetStatus(); // base virtual implementation runs
var mock = Mock.Of<ConcreteService>();
mock.CallBase = true;

var status = mock.Object.GetStatus(); // base virtual implementation runs

Burla's CallBase is also opt-in, but it only affects unmatched non-abstract virtual members on class mocks. Explicit setups still win. Interface mocks, abstract members, and non-virtual members do not gain fallback behavior from CallBase.

Protected members

using Moq.Protected;

mock.Protected()
    .Setup<string>("ComputeToken")
    .Returns("token");
// No Burla equivalent in 1.0.0.
// Protected-member mocking is intentionally deferred.

If your Moq tests depend on Moq.Protected, keep that part of the test as-is for now or refactor toward public/virtual seams before migrating. Protected-member setup and verification are intentionally unsupported in Burla 1.0.0.

Events (Event(...).Emit(...))

mock.Raise(
    x => x.PropertyChanged += null,
    new System.ComponentModel.PropertyChangedEventArgs("BaseUrl"));
mock.Event(nameof(INotifyPropertyChanged.PropertyChanged))
    .Emit(
        mock.Instance,
        new System.ComponentModel.PropertyChangedEventArgs("BaseUrl"));

Burla's event API is mock.Event(name).Emit(...). Unknown event names throw, known events without subscribers are ignored, and Burla supports any event delegate arity as long as the supplied arguments match the handler signature.

Quick reference

Moq Burla
new Mock<T>() Mock.Of<T>()
new Mock<T>(MockBehavior.Strict) Mock.Of<T>() (default)
new Mock<T>(MockBehavior.Loose) Mock.Of<T>(MockBehavior.Loose)
mock.Object mock.Object (compatibility alias; new Burla docs prefer .Instance)
It.IsAny<T>() It.IsAny<T>() (compatibility alias) or Arg.Any<T>()
It.Is<T>(...) It.Is<T>(...) (compatibility alias) or Arg.Is<T>(...)
It.IsIn(...) It.IsIn(...) (compatibility alias) or Arg.IsIn(...)
It.Ref<T>.IsAny It.Ref<T>.IsAny (compatibility alias) or Arg.Ref<T>.Any
.SetupGet(x => x.Property) .SetupGet(x => x.Property) (compatibility alias) or .Setup(x => x.Property)
.SetupSet(x => x.Property = It.IsAny<T>()) .SetupSet<T>(x => x.Property)
.SetupSequence().Returns().Returns() .ReturnsSequence(a, b)
sequence exhaustion returns default(T) sequence exhaustion throws SequenceExhaustedException by default
.ReturnsAsync(value) .ReturnsAsync(value)
.ThrowsAsync(ex) .Throws(ex) (auto-wraps async)
.Verify(expr, Times.Once()) Assert.Single(mock.CallsTo(...)) (preferred) or keep .Verify(expr, Times.Once()) for a low-diff port
.Verifiable() / .VerifyAll() CallsTo(...) assertions or explicit .Verify(...)
.VerifyNoOtherCalls() .VerifyNoOtherCalls()
.Invocations .RecordedCalls
.CallBase = true .CallBase = true
.Raise(x => x.Event += null, ...) .Event("EventName").Emit(...)
mock.Protected() unsupported / deferred in 1.0.0
.Reset() .Reset()
.Callback<T1, T2>((a, b) => ...) .Callback<T1, T2>((a, b) => ...)
new Mock<AbstractClass>() Mock.Of<AbstractClass>(MockBehavior.Loose)
new Mock<Class>(arg1) Mock.Of<Class>(MockBehavior.Loose, arg1)