NimbleMock: A Modern, Blazing-Fast Mocking Library for .NET (34x Faster Than Moq)
Hey devs! If you've ever felt the pain of verbose mocking setups, runtime overhead slowing down your test suites, or struggling to mock static methods like DateTime.Now without ugly wrappers, you're not alone. Traditional libraries like Moq and NSubstitute are great, but in large-scale .NET projects (especially with async-heavy code and generic repositories), they can feel dated and slow.
That's why I built NimbleMock — a zero-allocation, source-generated mocking library designed for modern .NET testing in 2025 and beyond.
Why I Built NimbleMock
Over the years, I've dealt with common mocking frustrations in enterprise .NET apps:
- Verbose setups for large interfaces (mocking 20+ methods just to test one).
- Performance hits from runtime proxies (Castle.DynamicProxy in Moq) — slow creation and verification in big test suites.
- Static/sealed mocking hell — no native support without paid tools or wrappers.
- Async and generics issues — unexpected null tasks or clunky type inference.
- Brittle tests — mocks that pass locally but fail in production because they don't match real API shapes.
Moq is battle-tested (and had its share of drama), NSubstitute is clean and readable, but neither fully leverages modern .NET features like source generators for zero overhead.
NimbleMock fixes these with compile-time magic: no runtime proxies, aggressive inlining, and stack allocation. Result? 34x faster mock creation and 3x faster verification than Moq, with zero allocations in typical scenarios.
Key Features
-
Partial mocks: Only mock what you need — unmocked methods throw clear
NotImplementedExceptionfor early detection. -
Native static/sealed mocking: Mock
DateTime.Now,Environment, etc., directly. -
First-class async support: Seamless for
Task<T>andValueTask<T>. -
Generic type inference: Full support for nested generics (e.g., EF Core
IQueryable<T>). - Fluent, chainable API: Inspired by the best of NSubstitute and Moq.
- Lie-proofing: Optional runtime validation against real staging APIs to catch outdated mock shapes.
-
Compile-time analyzers: Catch errors early (e.g., warn if you forget
SetupAsyncfor async methods).
MIT-licensed, open-source, and ready for contributions!
Quick Start
Install via NuGet:
dotnet add package NimbleMock
Basic example:
using NimbleMock;
public interface IUserRepository
{
User GetById(int id);
Task<bool> SaveAsync(User user);
}
var mock = Mock.Of<IUserRepository>()
.Setup(x => x.GetById(1), new User { Id = 1, Name = "Alice" })
.SetupAsync(x => x.SaveAsync(default!), true)
.Build();
var user = mock.Object.GetById(1); // Returns Alice
mock.Verify(x => x.GetById(1)).Once();
Partial mock (perfect for god-interfaces):
var mock = Mock.Partial<ILargeService>()
.Only(x => x.GetData(1), expectedData)
.Build();
// Unmocked methods throw for safety
Static mocking:
var staticMock = Mock.Static<DateTime>()
.Returns(d => d.Now, new DateTime(2025, 12, 25))
.Build();
staticMock.Verify(d => d.Now).Once();
Generics example:
public interface IRepository<T>
{
IQueryable<T> Query();
}
var mock = Mock.Of<IRepository<User>>()
.Setup(x => x.Query(), users.AsQueryable())
.Build();
var results = mock.Object.Query().Where(u => u.IsActive).ToList();
Performance Benchmarks
Run on .NET 8 (BenchmarkDotNet):
Mock Creation & Setup
| Library | Time (ns) | Memory Allocated | Gain vs Moq |
|---|---|---|---|
| Moq | 48,812 | 10.37 KB | Baseline |
| NSubstitute | 9,937 | 12.36 KB | ~5x faster |
| NimbleMock | 1,415 | 3.45 KB | 34x faster than Moq |
Method Execution Overhead
| Library | Time (μs) | Gain vs Moq |
|---|---|---|
| Moq | ~1.4 | Baseline |
| NSubstitute | ~1.6 | Slower |
| NimbleMock | ~0.6 | 2.3x faster |
Verification
| Library | Time (ns) | Memory Allocated | Gain vs Moq |
|---|---|---|---|
| Moq | 1,795 | 2.12 KB | Baseline |
| NSubstitute | 2,163 | 2.82 KB | Slower |
| NimbleMock | 585 | 0.53 KB | 3x faster than Moq |
Powered by source generators — no runtime reflection!
Run them yourself:
dotnet run --project tests/NimbleMock.Benchmarks --configuration Release
Migrating from Moq/NSubstitute
It's straightforward — fluent chains make it cleaner.
From Moq:
// Moq
var mock = new Mock<IUserRepository>();
mock.Setup(x => x.GetById(1)).Returns(user);
// NimbleMock
var mock = Mock.Of<IUserRepository>()
.Setup(x => x.GetById(1), user)
.Build();
Verification is more readable too.
Full migration guide in the repo!
Lie-Proofing: Avoid "Tests That Lie"
Unique feature: Validate mocks against real endpoints.
var result = await LieProofing.AssertMatchesReal<IApiService>("https://api.staging.example.com");
if (!result.IsValid)
{
// Log mismatches
}
Catches drifting APIs before production outages.
Final Thoughts
NimbleMock is built for .NET 8/9+ era: fast, safe, and joyful TDD. If you're tired of slow suites or static mocking hacks, give it a try!
Feedback welcome — what pains do you have with current mocking libs? Would you switch for native statics and this speed?
Star if you like it, and let's make testing fun again! 🚀
Tags: #dotnet #csharp #testing #tdd #opensource #mocking #performance
Top comments (0)