Meet Imposter: High-Performance Mocking for .NET Developers
When it comes to mocking in .NET, most of us have been reaching for Moq or NSubstitute for years. They’re battle-tested, well-documented, and pretty much every .NET project already has one of them. So it’s easy to never question whether something fundamentally faster might exist.
That’s exactly the promise of Imposter. It’s a mocking library built around C# source generators, and its approach is quite different from everything else out there. I took it for a spin, compared it against the two big players, and benchmarked them against each other 🧪.
Why is it faster?
To understand the performance story, it helps to know what Moq and NSubstitute are actually doing under the hood.
When you write this in Moq:
var mock = new Mock<IInventoryService>();
…it uses Castle DynamicProxy to dynamically generate a proxy class at runtime using reflection and IL emit. The proxy type itself is cached after the first creation, but every new Mock<T>() still allocates a fresh proxy instance, an interceptor, and the supporting setup state. Every method invocation on that mock then goes through the interceptor chain and a setup-matching step before returning a value.
NSubstitute works similarly. Both approaches are powerful and flexible, but they carry runtime overhead that scales with your test suite.
Imposter takes a completely different approach. It uses a Roslyn source generator to create your mock classes at compile time. You register the interfaces you want to mock from your test project using an assembly-level attribute:
// In your test project
[assembly: GenerateImposter(typeof(IInventoryService))]
After building, a concrete IInventoryServiceImposter class is generated. No proxy generation, no reflection, no Castle DynamicProxy. The generated code is just regular method implementations that match invocation arguments against the registered setups and dispatch to a stored delegate.
“If it builds, it runs” → no surprise runtime exceptions from proxy generation.
Let’s benchmark it
Each benchmark does the full cycle: create the mock, configure a return value, and invoke it. That’s the real overhead per test method 🔍.
[assembly: GenerateImposter(typeof(IInventoryService))]
[MemoryDiagnoser]
public class InventoryBenchmarks
{
[Benchmark]
public int Imposter()
{
var imposter = new IInventoryServiceImposter();
imposter.GetStock(1001, 3).Returns(42);
var sut = imposter.Instance();
return sut.GetStock(1001, 3);
}
[Benchmark]
public int Moq()
{
var mock = new Mock<IInventoryService>();
mock.Setup(m => m.GetStock(1001, 3)).Returns(42);
var sut = mock.Object;
return sut.GetStock(1001, 3);
}
[Benchmark]
public int NSubstitute()
{
var sub = Substitute.For<IInventoryService>();
sub.GetStock(1001, 3).Returns(42);
return sub.GetStock(1001, 3);
}
}
And here are the results, running on .NET 10.0.5:
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|---|---|---|---|---|---|---|
| Imposter | 308.5 ns | 6.15 ns | 17.93 ns | 0.3490 | 0.0033 | 2.85 KB |
| Moq | 1,749.5 ns | 34.05 ns | 47.73 ns | 0.5589 | 0.0038 | 4.58 KB |
| NSubstitute | 2,036.5 ns | 40.39 ns | 75.85 ns | 1.0185 | 0.0343 | 8.32 KB |
Imposter is about 5.7x faster than Moq and 6.6x faster than NSubstitute per test method, while also allocating meaningfully less memory. Because there’s no reflection-based dispatch, just pre-generated C# and a delegate invocation, the gap holds up consistently.
How does the API compare?
Now that you’ve seen the performance picture, let’s look at what it actually feels like to use. Here are all three side by side for a simple setup, invoke, and verify:
Moq:
var mock = new Mock<IInventoryService>();
mock.Setup(m => m.GetStock(It.IsAny<int>(), It.IsAny<int>())).Returns(42);
var result = mock.Object.GetStock(1001, 3);
mock.Verify(m => m.GetStock(1001, 3), Times.Once);
NSubstitute:
var sub = Substitute.For<IInventoryService>();
sub.GetStock(Arg.Any<int>(), Arg.Any<int>()).Returns(42);
var result = sub.GetStock(1001, 3);
sub.Received(1).GetStock(1001, 3);
Imposter (C# 14+):
var imp = IInventoryService.Imposter();
imp.GetStock(Arg<int>.Any(), Arg<int>.Any()).Returns(42);
var inventory = imp.Instance();
var result = inventory.GetStock(1001, 3);
imp.GetStock(1001, 3).Called(Count.Exactly(1));
The thing worth noticing here is the split between the imposter (for setup and verification) and the instance (the actual IInventoryService to inject into your system under test). A small but intentional design decision that makes the intent clearer.
Imposter also supports sequencing responses, argument matchers, async methods, properties, events, and indexers. So all the common mocking scenarios are covered.
Implicit vs Explicit mode
One other thing I liked is that Imposter lets you choose how strict your mocks should be. There are two modes:
// Unmocked members return defaults silently (like Moq's loose behavior)
var imp = new IInventoryServiceImposter(ImposterMode.Implicit);
// Any unmocked call throws. Nothing slips through unnoticed
var imp = new IInventoryServiceImposter(ImposterMode.Explicit);
Explicit mode is great for unit tests where precision matters. Implicit is useful when you only care about a specific part of the interface and want everything else to just return defaults without noise.
So, should you rip out Moq or NSubstitute from your existing project? Probably not. If you already have hundreds or thousands of unit tests written with either of them, the migration effort won’t justify the performance gains. Yes, you could let an AI agent handle a big chunk of the mechanical rewriting, and it would probably do a decent job. But even then, you’d still want to review the output carefully, re-run everything, and deal with the edge cases it gets wrong.
But if you’re starting a new project, Imposter is absolutely worth a look. Source-generated mocks, compile-time safety, no hidden runtime proxy failures, and a clean fluent API that feels familiar enough to pick up in an afternoon.