NSubstitute

Mocking library for .NET with a cleaner, more concise syntax than Moq — uses extension method syntax that reads more naturally. NSubstitute: var repo = Substitute.For<IAgentRepository>(); repo.GetByIdAsync(agentId).Returns(agent) stubs return; repo.Received(1).SaveAsync(agent) verifies call occurred; repo.DidNotReceive().DeleteAsync(Arg.Any<string>()) verifies method not called. No Lambda wrappers — setup and verify directly on the substituted interface. ReceivedWithAnyArgs(), Arg.Is<T>(), Arg.Do<T> for argument matching. Returns multiple values in sequence with Returns(first, second, third). NSubstitute's concise syntax reduces test boilerplate compared to Moq's Setup/Verify chains.

Evaluated Mar 06, 2026 (0d ago) v5.x
Homepage ↗ Repo ↗ Developer Tools dotnet csharp mocking unit-testing nsubstitute test-double substitute
&#9881; Agent Friendliness
68
/ 100
Can an agent use this?
&#128274; Security
93
/ 100
Is it safe for agents?
&#9889; Reliability
90
/ 100
Does it work consistently?

Score Breakdown

⚙ Agent Friendliness

MCP Quality
--
Documentation
88
Error Messages
85
Auth Simplicity
98
Rate Limits
98

🔒 Security

TLS Enforcement
95
Auth Strength
95
Scope Granularity
90
Dep. Hygiene
88
Secret Handling
95

Test-only mocking library. Same security considerations as Moq — don't use in production, ensure test agent stubs don't contain real credentials.

⚡ Reliability

Uptime/SLA
92
Version Stability
90
Breaking Changes
88
Error Recovery
90
AF Security Reliability

Best When

Your .NET agent team wants cleaner, less verbose mock syntax than Moq — NSubstitute's extension method approach reads more naturally and reduces test setup verbosity for agent service unit tests.

Avoid When

Your team is already invested in Moq, you need complex argument matchers, or you prefer Moq's explicit Setup/Verify pattern for clarity.

Use Cases

  • Clean agent repository mocking — var repo = Substitute.For<IAgentRepository>(); repo.FindActiveAsync().Returns(Task.FromResult(agents)); cleaner syntax than Moq's mock.Setup(r => r.FindActiveAsync()).ReturnsAsync(agents) for agent service unit tests
  • Agent service method verification — agentService.Received(1).ProcessTask(Arg.Is<string>(id => id.StartsWith('task-'))); verifies agent service called with task ID matching pattern; cleaner than Moq's Verify lambda
  • Throw exceptions from agent stubs — repo.GetByIdAsync(Arg.Any<string>()).Throws<NotFoundException>() makes stub throw on any ID call; test agent error handling paths
  • Capture agent arguments — repo.When(r => r.SaveAsync(Arg.Any<Agent>())).Do(ci => capturedAgent = ci.Arg<Agent>()); captures agent argument for assertions without Moq's Callback complexity
  • Sequential agent responses — llmClient.CompleteAsync(Arg.Any<string>()).Returns('first response', 'second response', 'third response') returns different responses on each successive agent call

Not For

  • Mocking static methods — NSubstitute cannot mock static/sealed classes without interfaces; same limitation as Moq; refactor agent code to use interfaces
  • Teams deeply invested in Moq — NSubstitute and Moq have different syntax; mixing both in same agent test project creates confusion; pick one and standardize
  • Complex argument matchers — Moq's It.Is<T>(x => complex expression) is more flexible than NSubstitute's Arg.Is; for complex agent argument matching scenarios, Moq may be cleaner

Interface

REST API
No
GraphQL
No
gRPC
No
MCP Server
No
SDK
Yes
Webhooks
No

Authentication

Methods: none
OAuth: No Scopes: No

Mocking library — no auth concepts.

Pricing

Model: open_source
Free tier: Yes
Requires CC: No

NSubstitute is BSD-3 licensed. Free for all use.

Agent Metadata

Pagination
none
Idempotent
Full
Retry Guidance
Not documented

Known Gotchas

  • Returns() must come after the method call expression — var result = repo.GetAsync(id); result.Returns(agent) is WRONG; correct: repo.GetAsync(id).Returns(Task.FromResult(agent)); NSubstitute intercepts the last call on the substitute; calling Returns() after other substitute operations configures wrong method for agent stubs
  • Received() checks all calls since creation — repo.Received(1).SaveAsync(agent) fails if SaveAsync was called 0 or 2+ times; use ClearReceivedCalls() between test phases if substitute used in multiple agent test operations; Received accumulates across all method calls not just the last
  • Arg.Any<T>() must match exact type — Arg.Any<string>() doesn't match Arg.Any<object>() even when string is assignable to object; for agent methods accepting object, use Arg.Any<object>() not Arg.Any<string>() in substitute argument matchers
  • Returns on async methods needs Task return — repo.GetAsync(id).Returns(agent) returns Task<Agent> containing agent; repo.GetAsync(id).Returns(Task.FromResult(agent)) is equivalent; both work but Returns with non-Task value for async methods causes incorrect behavior; NSubstitute wraps in Task automatically for Returns() with direct value
  • Partial substitutes for abstract classes lose real behavior — Substitute.ForPartsOf<AgentBase>() creates partial substitute; unset virtual methods call real implementation; non-virtual methods always call real implementation; for agent abstract classes with shared logic, test carefully which methods are being tested vs passed through
  • Thread safety: verifying after parallel agent calls may miss — NSubstitute call tracking is not fully thread-safe for concurrent verifications; for agent tests with parallel code under test, add synchronization before Received() verification; race condition in call tracking causes intermittent Received failures in parallel agent integration tests

Alternatives

Full Evaluation Report

Detailed scoring breakdown, competitive positioning, security analysis, and improvement recommendations for NSubstitute.

$99

Scores are editorial opinions as of 2026-03-06.

5229
Packages Evaluated
26151
Need Evaluation
173
Need Re-evaluation
Community Powered