Imposter: a source-generated mocking library for .NET
Mocking library with the perfect balance of Performance and Intuitive API
At a glance
- What is it?
- Imposter is a mocking library for .NET that generates the mock type at compile time instead of building it at run time, which is where its speed comes from. The API is the familiar arrange, act, assert shape with argument matchers and chained return values, and the interesting decisions are the ones about strictness, about the fact that the library mocks classes as well as interfaces, and about running the comparison against other libraries on every commit.
- Who is it for?
- Imposter fits a .NET team with a large test suite where the mocking library shows up in the wall clock time of the build, and it fits a team that has been bitten by a loosely typed mock throwing at run time. It does not fit a project that needs to mock a sealed class or a type it does not own, and it does not fit a team that wants the dynamic proxy style where the mock type exists only at run time.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the library claims, and how it claims it
Imposter describes itself as a mocking library with the perfect balance of performance and an intuitive API, and the sentence in the readme that explains the design philosophy is the one to start with. It delivers top-tier performance without sacrificing a natural, intuitive API. While being about ten times faster than NSubstitute and up to about fifty times faster than Moq in common mocking scenarios, it is not the fastest mocking library available. Instead, it focuses on achieving a balance between performance and API ease of use.
That is a careful piece of positioning, and the disclaimer in the middle of it is the part that makes the claim credible. A library that announced itself as the fastest would be easy to dismiss, so the readme says plainly that faster options exist, and then explains what the library does with that fact: it trades the last of the speed for an API that reads the way a person expects.
The numbers themselves are attached to a specific scenario, and the readme describes it. Each library in the comparison sets up a square function to return the input multiplied by itself, and then calls it for one, ten, one hundred, and one thousand iterations. The results are published as a chart in the readme and as a Markdown file in the repository, and the code for the benchmarks is in the repository too, so the comparison can be read rather than believed.
The claim about the numbers being current is backed by automation. A benchmarks workflow reruns the comparison on a hosted runner after every change to the code on the master branch, so the table always reflects the latest build. And the readme adds the caveat that matters for reading any benchmark: shared runners are slower and noisier than a desktop machine, so the numbers are meaningful when you compare the libraries within the table rather than against an absolute figure you remember from your own machine.
That last point is the difference between a benchmark section and a marketing section, and it is why the rest of the claim can be taken seriously.
The generated mock, and the two ways to call it
The quick start is four steps, and the first of them is the mechanism that explains the performance.
You add the package to your test project. You pick a target, in this case an interface with a single method that adds two integers. You then apply the generate attribute in the tests project, at assembly level, naming the type to impersonate. The attribute is the interesting part, because it is a source generator, and a source generator runs at compile time. The mock type is written out as C# by the compiler rather than assembled at run time by a proxy library, and that is where the order-of-magnitude difference comes from.
The attribute is applied at assembly level, so a test project can declare all of its mockable types in one place, and it names the type it wants generated, which means the generated code is specific to that interface.
Using the mock has two forms, and the readme shows both. The newer one is a static call on the interface itself, and the older one is a constructor on a generated class.
// c# 14
var imposter = ICalculator.Imposter();
// c# 9 - 13
// var imposter = new ICalculatorImposter();The comment in the code block is the documentation. The newer syntax is a static method on the interface type, which reads as if the interface has a factory method, and the older syntax constructs a generated class whose name is the interface name with a suffix. Both produce the same object.
The arrangement itself is the familiar shape, with argument matchers, a return value, and a chain.
imposter.Add(Arg<int>.Any(), Arg<int>.Any())
.Returns(1)
.Then()
.Returns(2);
var calculator = imposter.Instance();
calculator.Add(1, 2); // 1
calculator.Add(1, 2); // 2The matchers are the static helper that stands for any value of a type, the returns sets the value for the first call, the then starts a new arrangement, and the instance method produces the object you exercise. The two calls at the end return one and then two, in that order, which is the sequential return behaviour that the older mocking libraries made popular and that the chain syntax expresses directly.
Strong typing, and the members it will mock
The section on typing is the shortest in the readme and carries the most weight, because it describes a class of bug that mocking libraries are notorious for.
The claim is that loosely typed callbacks can easily cause runtime exceptions, and that Imposter enforces strong typing across the entire mocking pipeline, eliminating type mismatch errors for good. The link goes to a documentation page on method callbacks, so the detail is in the docs rather than the readme.
The reason this matters is worth stating plainly. A callback that a test author writes by hand, with a delegate type that does not match the method it is standing in for, compiles cleanly in a loosely typed library and fails at the moment the test runs. If the failing test is one that mocks a rarely exercised path, that failure can appear in a run long after the code that caused it was written, and the stack trace points at the test rather than at the mistake. Removing the possibility of the mismatch is worth a great deal in a large suite.
The coverage of what can be mocked is broader than the interface-only model. The readme notes that Imposter is not limited to interfaces, and that it can impersonate non-sealed classes and their protected members, with a link to the documentation for that. Non-sealed is the operative word, because a sealed class cannot be derived from, and that remains the boundary.
Generics are fully supported, again with a link to a page of their own, which is the feature that dynamic proxy libraries have historically handled worst, since a generic type needs a closed type at run time and a static call site does not.
And the list of what can be impersonated is longer than methods. Properties, events, and indexers are all supported, each with its own documentation page, and the feature list at the bottom of the readme repeats them along with the class impersonation, the generics, the two modes, the base implementation, async support, and protected members. The separate page for each feature tells you that the documentation is organised around what a user might want to do rather than around the shape of the library.
Implicit and explicit, and the one that finds your bugs
The two modes are the feature that will change how a test suite behaves, and the readme describes both in a single sentence each.
Implicit mode returns defaults silently, which the readme calls great for prototyping. Explicit mode throws on any call without a setup, which the readme calls ideal for unit tests that must be precise.
That difference is worth dwelling on, because it is the classic trade-off between a mock that is convenient and a mock that is honest. A loose mock returns zero, or null, or an empty collection for anything the test did not arrange, and that behaviour lets a test pass while the code under test takes a branch the test author did not think about. A strict mock throws instead, and the test fails with a message naming the call that was not set up.
So implicit mode finds the test that is easy to write while you are still working out the shape of the code, and explicit mode finds the test that has drifted out of step with the code once the shape is settled. The readme's framing is that the second is what you want in unit tests that must be precise, which is a claim about a mature suite rather than a developing one.
The mode is a setting on the mock, which means it can be chosen per test rather than per project, and that is the useful middle ground: a suite can be strict where it matters and loose where it is scaffolding.
The feature list also records a use-base-implementation option, which is the arrangement where a mock delegates to the real implementation for the members that were not set up. That is the counterpart to strict mode, and it is what you want when a class has a lot of behaviour and you only care about one method of it.
Thread safety, the package, and the repository around it
Two claims in the readme are about the environment rather than the API, and both are the kind that a test suite depends on.
The first is that Imposter is built to work reliably in multi-threaded and parallel test environments, and it says it is thread-safe by design. For a library that generates its type at compile time and then behaves like a small object with a set of configured responses, thread safety is mostly a question about how the configured state is stored and whether a call during a parallel run can observe a half-written arrangement. The claim is worth checking in your own suite, because a mocking library that is not thread safe is not usable in a project that runs its tests in parallel, which most do by default.
The second is the packaging. The library is on NuGet, and the install is a single command.
dotnet add package ImposterThe header of the readme carries four other badges, and each one is a signal about how the project is run. There is a build, test, and format verification workflow, which means the same check that runs locally is what runs in continuous integration. There is a CodeQL workflow, which is the static analysis GitHub runs on every repository, and for a library that parses or reflects over your types, that analysis is more than a formality. There is an OpenSSF scorecard badge, which is the supply chain security scorecard, and its presence suggests the project is trying to be evaluated on the criteria a security team would use. And there is the NuGet version badge.
The repository layout matches a project with a serious testing setup. There is a solution file in the newer format, a shared props file for the Roslyn build, a global file pinning the SDK version, an install script for the .NET SDK, a build scripts directory, a benchmarks directory, a playground for experiments, a samples directory with a service project and its test project, a tests directory, and a documentation directory. There is also a Husky directory for the git hooks, an editor configuration, a security policy, and a contributing guide.
The release history shows how young and how active the project is. Version 1.0.0 came on 2026-10-14, followed by 1.0.1 two days later and 1.0.2 four days after that, and the notes for the last one say that async methods now wait for their callbacks. Three releases in a week, and the third one fixing a behaviour that anybody who had written an asynchronous test against version 1.0.0 would have hit immediately.
Editorial conclusion
Imposter fits a .NET team with a large test suite where the mocking library shows up in the wall clock time of the build, and it fits a team that has been bitten by a loosely typed mock throwing at run time. It does not fit a project that needs to mock a sealed class or a type it does not own, and it does not fit a team that wants the dynamic proxy style where the mock type exists only at run time. Verify first that your project can use a source generator, since that is the mechanism behind the speed, that the language version of your project is one the newer call syntax supports, since the readme shows a difference between the recent and the older forms, and that the strictness mode you choose matches how your tests are written, because the explicit mode turns an unconfigured call into a failure rather than a default value.
Frequently asked questions
What is the Imposter mocking library?
It is a mocking library for .NET that generates the mock type at compile time through a source generator, rather than building it at run time. The readme claims it is about ten times faster than NSubstitute and up to about fifty times faster than Moq in common scenarios, while being explicit that it is not the fastest available.
How do I create a mock with Imposter?
Add the package to the test project, apply the generate attribute at assembly level naming the interface or class you want to mock, then create the imposter with the static call on the type in the recent language version, or with the constructor of the generated class in the older one. The instance method gives you the object to exercise.
What is the difference between the implicit and explicit modes in Imposter?
Implicit mode returns defaults silently for any member without a setup, which suits prototyping. Explicit mode throws on any call without a setup, which suits unit tests that must be precise, because a call the test never arranged surfaces as a failure rather than as a quietly wrong value.
Can Imposter mock classes and not just interfaces?
Yes. It can impersonate non-sealed classes and their protected members, as well as methods, properties, events, and indexers, and it supports generics fully. A sealed class remains outside what it can mock, since it cannot be derived from.
How are the Imposter benchmark results kept up to date?
A dedicated workflow reruns the comparison on a hosted runner after every change to the master branch, so the published chart and Markdown table reflect the latest build. The readme also notes that shared runners are slower and noisier than a desktop machine, so the numbers are best read as a comparison within the table.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/themidnightgospel-imposter)Was this useful?