Skip to content

Migrating from Moq

Skugga achieves 100% practical parity with Moq's core API (937 tests covering all major features). The API is intentionally identical for seamless migration.

Migration Checklist

  • Replace new Mock<T>() with Mock.Create<T>()
  • Remove .Object property access (Skugga mock IS the object)
  • Replace ItExpr.* with It.* in Protected() setups
  • All other API calls remain identical
  • Test early and often -- Skugga's strict type checking catches issues at compile time

Quick Examples

Basic Setup/Returns

csharp
// Moq
var moqMock = new Mock<IEmailService>();
moqMock.Setup(x => x.GetEmail(1)).Returns("test@test.com");
var service = moqMock.Object;

// Skugga -- identical setup API
var skuggaMock = Mock.Create<IEmailService>();
skuggaMock.Setup(x => x.GetEmail(1)).Returns("test@test.com");
// No .Object property needed -- mock IS the object

Verify with Times

csharp
// Moq
moqMock.Verify(x => x.SendEmail(It.IsAny<string>()), Times.Exactly(3));

// Skugga -- identical
skuggaMock.Verify(x => x.SendEmail(It.IsAny<string>()), Times.Exactly(3));

Properties

csharp
// Moq
moqMock.Setup(x => x.ServerUrl).Returns("https://api.example.com");
moqMock.SetupSet(x => x.ServerUrl = "https://new.example.com").Verifiable();

// Skugga -- identical
skuggaMock.Setup(x => x.ServerUrl).Returns("https://api.example.com");
skuggaMock.SetupSet(x => x.ServerUrl = "https://new.example.com").Verifiable();

Setup Sequences

csharp
// Moq
moqMock.SetupSequence(x => x.GetNext())
       .Returns(1)
       .Returns(2)
       .Throws(new InvalidOperationException());

// Skugga -- identical
skuggaMock.SetupSequence(x => x.GetNext())
           .Returns(1)
           .Returns(2)
           .Throws(new InvalidOperationException());

Protected Members

csharp
// Moq
var moqMock = new Mock<AbstractService>();
moqMock.Protected()
       .Setup<string>("ProcessCore", ItExpr.IsAny<string>())
       .Returns("mocked");

// Skugga -- uses It.IsAny instead of ItExpr
var skuggaMock = Mock.Create<AbstractService>();
skuggaMock.Protected()
           .Setup<string>("ProcessCore", It.IsAny<string>())
           .Returns("mocked");

Strict Mocks

csharp
// Moq
var moqMock = new Mock<IService>(MockBehavior.Strict);

// Skugga -- identical
var skuggaMock = Mock.Create<IService>(MockBehavior.Strict);

Multiple Interfaces

csharp
// Moq
var moqMock = new Mock<IEmailService>();
moqMock.As<IDisposable>().Setup(x => x.Dispose());

// Skugga -- identical
var skuggaMock = Mock.Create<IEmailService>();
skuggaMock.As<IDisposable>().Setup(x => x.Dispose());

Feature Comparison

FeatureMoqSkuggaNotes
Core Setup/ReturnsYesYesIdentical API
Verify with TimesYesYesIdentical API
Properties (Get/Set)YesYesIdentical API
CallbacksYesYesIdentical API
Argument MatchersYesYesIt.IsAny, It.Is, It.IsIn, It.IsNotNull, It.IsRegex
Strict MocksYesYesMockBehavior.Strict
Setup SequencesYesYesIdentical API
Protected MembersYesYes.Protected().Setup<T>("MethodName")
Generic Type ParametersYesYesFull support
Multiple Interfaces (As)YesYesmock.As<IDisposable>()
Custom MatchersYesYesMatch.Create<T>(predicate)
Events (Raise)YesYesIdentical API
Mock.Get<T>()YesYesRetrieve mock from object
Native AOT SupportNoYesMoq crashes in AOT
Zero ReflectionNoYesCompile-time generation
AutoScribeNoYesSelf-writing tests
Chaos ModeNoYesResilience testing
Zero-Alloc GuardNoYesPerformance enforcement

AOT Limitation: Mock.Of<T>(expression)

Skugga does not support Mock.Of<T>(expression) syntax due to a fundamental C# interceptor limitation:

csharp
// NOT SUPPORTED in Skugga
var mock = Mock.Of<IFoo>(f => f.Name == "bar" && f.Count == 42);

// Use this pattern instead
var mock = Mock.Create<IFoo>();
mock.Setup(f => f.Name).Returns("bar");
mock.Setup(f => f.Count).Returns(42);

Why? C# interceptors only work on direct call sites in user code. When Mock.Of() internally calls Mock.Create(), that library-internal call cannot be intercepted without runtime IL generation (which breaks AOT compatibility).

Released under the MIT License.