Mocking Frameworks
Moq, NSubstitute, Mockito, unittest.mock, Sinon and the rest — what each ecosystem's mocking library does well, and the failure modes they share.
2 min read · updated 19 September 2026
Every ecosystem has one, they all do roughly the same thing, and the differences that matter are in verification style and in how gracefully they fail.
#.NET
Moq — the most used, lambda-based setup, and the most complete verification API.
var gateway = new Mock<IPaymentGateway>();
gateway.Setup(g => g.ChargeAsync(It.Is<ChargeRequest>(r => r.Cents == 4_000)))
.ReturnsAsync(ChargeResult.Succeeded("pi_1"));
// ...
gateway.Verify(g => g.ChargeAsync(It.IsAny<ChargeRequest>()), Times.Once);NSubstitute — the same capability with a much lighter syntax. Many teams prefer it purely for readability.
var gateway = Substitute.For<IPaymentGateway>();
gateway.ChargeAsync(Arg.Is<ChargeRequest>(r => r.Cents == 4_000))
.Returns(ChargeResult.Succeeded("pi_1"));
// ...
await gateway.Received(1).ChargeAsync(Arg.Any<ChargeRequest>());FakeItEasy — a third option, similar in spirit to NSubstitute.
All three can only substitute what is virtual or interface-based. That constraint is a feature: it forces dependency injection rather than letting you paper over a hard-coded dependency.
#Java
Mockito is effectively the standard.
@ExtendWith(MockitoExtension.class)
class CheckoutTest {
@Mock PaymentGateway gateway;
@Captor ArgumentCaptor<ChargeRequest> request;
@Test
void chargesTheOrderTotal() {
when(gateway.charge(any())).thenReturn(ChargeResult.succeeded("pi_1"));
new Checkout(gateway).pay(order);
verify(gateway).charge(request.capture());
assertThat(request.getValue().cents()).isEqualTo(4_000);
}
}ArgumentCaptor is the nicest thing in Mockito: rather than expressing the
expectation as a matcher, capture the argument and assert on it with your
normal assertion library. The failure message is then about the value, not
about "wanted but not invoked".
mockStatic exists for legacy code. Treat reaching for it as a finding:
something untestable is being worked around rather than fixed. See
writing testable code.
#Python
unittest.mock is in the standard library and does everything.
from unittest.mock import Mock, patch, ANY
def test_charges_the_order_total():
gateway = Mock()
gateway.charge.return_value = ChargeResult(ok=True, id="pi_1")
Checkout(gateway).pay(order)
gateway.charge.assert_called_once_with(cents=4_000, token=ANY)
# Patch where the name is USED
@patch("checkout.service.fetch_rates", side_effect=TimeoutError)
def test_falls_back_when_rates_time_out(fetch_rates):
assert quote(order).cents == FALLBACK_CENTSTwo Python-specific hazards:
Patch location. patch("module.where.it.is.used"), not where it is
defined. This catches everyone at least once.
Mock() accepts anything. gateway.chrage(...) returns a new Mock
and the test passes. Use autospec=True or create_autospec so the double
has the real signature:
gateway = create_autospec(PaymentGateway, instance=True)
gateway.chrage(...) # AttributeError, as it should be#JavaScript and TypeScript
Jest's built-in mocking covers most needs. Sinon is the framework-agnostic alternative, used with Mocha and in browser contexts.
const sandbox = sinon.createSandbox();
afterEach(() => sandbox.restore());
const charge = sandbox.stub(gateway, 'charge').resolves({ id: 'pi_1' });
await new Checkout(gateway).pay(order);
sinon.assert.calledWithMatch(charge, { cents: 4_000 });TypeScript adds a specific pleasure and a specific pain: jest.Mocked<T>
and ts-mockito give typed doubles, and mocking a module with a large
surface means satisfying its whole type. That friction is a useful signal —
see the boundary rule below.
#Ruby
RSpec's built-ins, with one strong recommendation: use instance_double
rather than double, so the framework fails when you stub a method that
does not exist. See RSpec.
#The failure modes they all share
Over-specification. Verifying call counts, argument order and exact values couples the test to the implementation. Ask: if the code were refactored without changing behaviour, would this test still pass?
Doubles that lie. A stub returns what you told it to. The real dependency may return something else, throw, or be slow. Back mocked units with real integration tests or contract tests.
Mocking your own code. A test that mocks four modules you wrote is testing a wiring diagram. See unit testing without mocks.
Unverified doubles. Any framework that lets you stub a method that does
not exist will eventually let you ship a test that proves nothing. Use the
verifying variant everywhere it exists: autospec in Python,
instance_double in Ruby, generic type parameters in .NET and Java.
#The boundary rule
The one rule that resolves most of this:
Mock at the process boundary. Use real objects inside it.
The database, the HTTP client, the broker, the clock, the filesystem, the third-party SDK — double them. Your own value objects, domain entities and pure functions — construct them. Where the tooling makes that awkward, the awkwardness is usually pointing at the design.
Common questions
- Which mocking framework should I use?
- Whichever is standard in your ecosystem — Moq or NSubstitute in .NET, Mockito in Java, unittest.mock in Python, Jest or Sinon in JavaScript, RSpec's built-in doubles in Ruby. The differences between them are small next to the difference between using them well and badly.
- Can I mock static methods?
- In most languages, only with tooling that rewrites bytecode or intercepts at the runtime level — Mockito's mockStatic, Moq's limitations here, typemock-style products. It is almost always better to treat the need as a design signal and wrap the static call in something injectable.
- Why do people warn against mocking?
- Because a mock encodes an assumption about a collaborator, and a suite full of mocks is a suite full of assumptions that are never checked against reality. Used at the process boundary they are indispensable; used between your own classes they produce tests that break on every refactor.
Runnable samples for this page
last test results ↗- C# (NSubstitute)
dotnet/Tests.XUnit/tools/mocking-frameworks - TypeScript
typescript/src/tools/mocking-frameworks
Working tests, not fragments — they run in CI on every push to 8exgh/endtoendtester-samples.
Was this page useful?
Related topics
- Test DoublesDummy, stub, spy, mock and fake — Meszaros's five kinds of test double, what each is for, and why using the words precisely makes code reviews shorter.
- MoqThe .NET mocking library in practical detail — setups, argument matchers, verification, loose versus strict behaviour, and what to use instead where it strains.
- Jest MockingModule mocks, spies, manual mocks and timer control — how Jest's mocking works, and the patterns that keep it from taking over a suite.
- Unit Testing with MocksThe solitarist approach — isolate the unit behind test doubles. What it buys, the coupling it creates, and the three rules that keep a mock-heavy suite maintainable.
- Unit Testing without MocksThe sociable approach — real collaborators, state-based assertions, and tests that survive refactoring. What it buys, where it breaks down, and how to keep it fast.
- Writing Tests in JavaJUnit 5, Mockito, AssertJ and Testcontainers — the stack that has been stable for a decade, plus Spring's test slices and what they cost.
- Writing Tests in Pythonpytest fixtures, the patching rules that catch everyone, async testing, and the toolchain for a Python project that has to hold up in CI.
- JUnit 5The Java standard — Jupiter's extension model, parameterised tests, nested classes, assertions with AssertJ, and running it all in CI.