DO

dotnet-testing-nsubstitute-mocking

Provides guidance on using NSubstitute for mocking, stubbing, and testing dependencies in .NET applications.

Install

mkdir -p .claude/skills/dotnet-testing-nsubstitute-mocking && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/17722" && unzip -o skill.zip -d .claude/skills/dotnet-testing-nsubstitute-mocking && rm skill.zip

Installs to .claude/skills/dotnet-testing-nsubstitute-mocking

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Using NSubstitute to create test doubles (Mock, Stub, Spy) specialized skill. Used when isolating external dependencies, simulating interface behavior, and validating method calls. Covers Substitute.For, Returns, Received, Throws and complete guidance. Keywords: mock, stub, spy, nsubstitute, mock, test double, test double, IRepository, IService, Substitute.For, Returns, Received, Throws, Arg.Any, Arg.Is, isolate dependencies, simulate external services, dependency injection testing
486 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Beginner

Key capabilities

  • Create test doubles using NSubstitute for interfaces and classes
  • Set up return values for methods with exact or any parameter matching
  • Configure methods to throw exceptions synchronously or asynchronously
  • Verify method calls, including count and argument specifics
  • Capture arguments passed to substituted methods
  • Validate ILogger calls in unit tests

How it works

NSubstitute creates test doubles for interfaces or virtual members of classes, allowing configuration of return values, exceptions, and verification of method calls to isolate dependencies.

Inputs & outputs

You give it
Interface or class to be substituted, desired method behaviors, and expected interactions
You get back
Test doubles (Mock, Stub, Spy) that isolate dependencies and enable behavior verification

When to use dotnet-testing-nsubstitute-mocking

  • Isolating external dependencies
  • Mocking interfaces for testing
  • Validating method calls
  • Simulating external service behavior

About this skill

Source: kevintsengtw/dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.

NSubstitute Test Double Guide

Applicable Scenarios

This skill focuses on creating and managing test doubles using NSubstitute, covering the five types of Test Doubles, dependency isolation strategies, behavior setup and validation best practices.

Why Do We Need Test Doubles?

Real-world code usually depends on external resources, which make tests:

  1. Slow - Need actual database operations, file systems, networks
  2. Unstable - External service failures cause test failures
  3. Difficult to Repeat - Time, random numbers cause inconsistent results
  4. Environment Dependent - Need specific external environment setup
  5. Development Blocking - Must wait for external systems to be ready

Test doubles allow us to isolate these dependencies and focus on testing business logic.

Prerequisites

Package Installation

<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />
```text

### Basic using Directives

```csharp
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
using AwesomeAssertions;
using Microsoft.Extensions.Logging;
```text

## Test Double Five Types

According to Gerard Meszaros's definition in "xUnit Test Patterns", test doubles are divided into five types:

| Type | Purpose | NSubstitute Equivalent |
|------|------|-------------------|
| **Dummy** | Fill objects, only satisfy method signatures | `Substitute.For<T>()` without setting any behavior |
| **Stub** | Provide default return values, set test scenarios | `.Returns(value)` |
| **Fake** | Simplified implementation with real logic | Manual interface implementation (like `FakeUserRepository`) |
| **Spy** | Record calls, verify afterwards | `.Received()` validation |
| **Mock** | Default expected interactions, test fails if not satisfied | `.Received(n)` strict validation |

> Full code examples for each type please refer to [references/test-double-types.md](references/test-double-types.md)

## NSubstitute Core Functions

### Basic Substitution Syntax

```csharp
// Create interface substitute
var substitute = Substitute.For<IUserRepository>();

// Create class substitute (needs virtual members)
var classSubstitute = Substitute.For<BaseService>();

// Create multiple interface substitute
var multiSubstitute = Substitute.For<IService, IDisposable>();
```text

### Return Value Setup

#### Basic Return Values

```csharp
// Exact parameter matching
_repository.GetById(1).Returns(new User { Id = 1, Name = "John" });

// Any parameter matching
_service.Process(Arg.Any<string>()).Returns("processed");

// Return sequence values
_generator.GetNext().Returns(1, 2, 3, 4, 5);
```text

#### Conditional Return Values

```csharp
// Use delegate to calculate return value
_calculator.Add(Arg.Any<int>(), Arg.Any<int>())
           .Returns(x => (int)x[0] + (int)x[1]);

// Condition matching
_service.Process(Arg.Is<string>(x => x.StartsWith("test")))
        .Returns("test-result");
```text

#### Throw Exceptions

```csharp
// Synchronous method throws exception
_service.RiskyOperation()
        .Throws(new InvalidOperationException("Something went wrong"));

// Async method throws exception
_service.RiskyOperationAsync()
        .Throws(new InvalidOperationException("Async operation failed"));
```text

### Argument Matchers

```csharp
// Any value
_service.Process(Arg.Any<string>()).Returns("result");

// Specific condition
_service.Process(Arg.Is<string>(x => x.Length > 5)).Returns("long-result");

// Argument capture
string capturedArg = null;
_service.Process(Arg.Do<string>(x => capturedArg = x)).Returns("result");
_service.Process("test");
capturedArg.Should().Be("test");

// Argument check
_service.Process(Arg.Is<string>(x =>
{
    x.Should().StartWith("prefix");
    return true;
})).Returns("result");
```text

### Call Verification

```csharp
// Verify was called (at least once)
_service.Received().Process("test");

// Verify call count
_service.Received(2).Process(Arg.Any<string>());

// Verify was not called
_service.DidNotReceive().Delete(Arg.Any<int>());

// Verify any argument call
_service.ReceivedWithAnyArgs().Process(default);

// Verify call order
Received.InOrder(() =>
{
    _service.Start();
    _service.Process();
    _service.Stop();
});
```text

## Practical Patterns

Covers five common NSubstitute practical patterns, including complete code examples:

| Pattern | Description |
|------|------|
| Pattern 1: Dependency Injection and Test Setup | FileBackupService complete example, including constructor injection and SUT setup |
| Pattern 2: Mock vs Stub Differences | Stub focuses on state return values vs Mock focuses on interaction behavior validation |
| Pattern 3: Async Method Testing | `Returns(Task.FromResult(...))` and `.Throws()` patterns |
| Pattern 4: ILogger Validation | Validate underlying `Log` method bypassing extension method limitations |
| Pattern 5: Complex Setup Management | Base test class managing shared Substitute setups |

> Full code examples please refer to [references/practical-patterns.md](references/practical-patterns.md)

## Advanced Argument Matching Techniques

### Complex Object Matching

```csharp
[Fact]
public void CreateOrder_CreateOrder_ShouldStoreCorrectOrderData()
{
    var repository = Substitute.For<IOrderRepository>();
    var service = new OrderService(repository);

    service.CreateOrder("Product A", 5, 100);

    // Verify object properties
    repository.Received(1).Save(Arg.Is<Order>(o =>
        o.ProductName == "Product A" &&
        o.Quantity == 5 &&
        o.Price == 100));
}
```text

### Argument Capture and Validation

```csharp
[Fact]
public void RegisterUser_RegisterUser_ShouldGenerateCorrectHashPassword()
{
    var repository = Substitute.For<IUserRepository>();
    var service = new UserService(repository);

    User capturedUser = null;
    repository.Save(Arg.Do<User>(u => capturedUser = u));

    service.RegisterUser("[email protected]", "password123");

    capturedUser.Should().NotBeNull();
    capturedUser.Email.Should().Be("[email protected]");
    capturedUser.PasswordHash.Should().NotBe("password123"); // Should be hashed
    capturedUser.PasswordHash.Length.Should().BeGreaterThan(20);
}
```text

## Common Pitfalls and Best Practices

### Recommended Practices

1. **Target interfaces rather than implementations for Substitutes**

    ```csharp
    // Correct: target interface
    var repository = Substitute.For<IUserRepository>();

    // Wrong: target concrete class (unless has virtual members)
    var repository = Substitute.For<UserRepository>();
```text

2. **Use meaningful test data**

    ```csharp
    // Correct: clearly express intent
    var user = new User { Id = 123, Name = "John Doe", Email = "[email protected]" };

    // Wrong: meaningless data
    var user = new User { Id = 1, Name = "test", Email = "[email protected]" };
```text

3. **Avoid over-verification**

    ```csharp
    // Correct: only verify important behaviors
    _emailService.Received(1).SendWelcomeEmail(Arg.Any<string>());

    // Wrong: verify all internal implementation details
    _repository.Received(1).GetById(123);
    _repository.Received(1).Update(Arg.Any<User>());
    _validator.Received(1).Validate(Arg.Any<User>());
```text

4. **Clear distinction between Mock and Stub**

    ```csharp
    // Correct: Stub for setting scenarios, Mock for validating behaviors
    var stubRepository = Substitute.For<IUserRepository>(); // Stub
    var mockLogger = Substitute.For<ILogger>(); // Mock

    stubRepository.GetById(123).Returns(user);
    service.ProcessUser(123);
    mockLogger.Received(1).LogInformation(Arg.Any<string>());
```text

### Practices to Avoid

1. **Avoid simulating value types**

    ```csharp
    // Wrong: DateTime is value type
    var badDate = Substitute.For<DateTime>();

    // Correct: abstract time provider
    var dateTimeProvider = Substitute.For<IDateTimeProvider>();
    dateTimeProvider.Now.Returns(new DateTime(2024, 1, 1));
```text

2. **Avoid tight coupling between tests and implementations**

    ```csharp
    // Wrong: test implementation details
    _repository.Received(1).Query(Arg.Any<string>());
    _repository.Received(1).Filter(Arg.Any<Expression<Func<User, bool>>>());

    // Correct: test behavior results
    var users = service.GetActiveUsers();
    users.Should().HaveCount(2);
```text

3. **Avoid overly complex setups**

    ```csharp
    // Wrong: too many Substitutes (may violate SRP)
    var sub1 = Substitute.For<IService1>();
    var sub2 = Substitute.For<IService2>();
    var sub3 = Substitute.For<IService3>();
    var sub4 = Substitute.For<IService4>();

    // Correct: reconsider class responsibilities
    // Consider whether violating single responsibility principle, needs refactoring
```text

## Identifying Dependencies to Substitute

### Should Substitute

- External API calls (IHttpClient, IApiClient)
- Database operations (IRepository, IDbContext)
- File system operations (IFileSystem)
- Network communication (IEmailService, IMessageQueue)
- Time dependencies (IDateTimeProvider, TimeProvider)
- Random number generation (IRandom)
- Expensive calculations (IComplexCalculator)
- Logging services (ILogger<T>)

### Should Not Substitute

- Value objects (DateTime, string, int)
- Simple data transfer objects (DTO)
- Pure function tools (like AutoMapper's IMapper, consider using real instance)
- Framework core classes (unless explicitly needed)

## Troubleshooting

### Q1: How to test classes without interfaces?

**A:** Ensure members to be simulated are virtual:

```csharp
public class BaseService
{
    public virtual string GetData() => "real data";
}

var substitute = Substitute.For<BaseService>();
substitute.GetData().Returns("t

---

*Content truncated.*

When not to use it

  • When the project already uses Moq
  • When Moq-specific advanced features are required

Prerequisites

NSubstitute NuGet packagexunit NuGet packageMicrosoft.Extensions.Logging NuGet packageAwesomeAssertions NuGet package

Limitations

  • Class substitution requires virtual members
  • Does not provide real logic for Fake test doubles, requiring manual implementation

How it compares

This skill uses NSubstitute's concise syntax for test doubles, offering a simpler alternative to manual interface implementations or other mocking frameworks.

Compared to similar skills

dotnet-testing-nsubstitute-mocking side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
dotnet-testing-nsubstitute-mocking (this skill)04moNo flagsBeginner
performance-benchmark34moNo flagsIntermediate
mutation-testing0ReviewAdvanced
csharp-pro93moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

performance-benchmark

dotnet

Generate and run ad hoc performance benchmarks to validate code changes. Use this when asked to benchmark, profile, or validate the performance impact of a code change in dotnet/runtime.

328

mutation-testing

SebastienDegodez

Use when running mutation testing, killing mutants, verifying test quality, checking mutation score, or analyzing survivors after the test baseline is green

00

csharp-pro

sickn33

Write modern C# code with advanced features like records, pattern matching, and async/await. Optimizes .NET applications, implements enterprise patterns, and ensures comprehensive testing. Use PROACTIVELY for C# refactoring, performance optimization, or complex .NET solutions.

953

backend-testing

exceptionless

Backend testing with xUnit, Foundatio.Xunit, integration tests with AppWebHostFactory, FluentClient, ProxyTimeProvider for time manipulation, and test data builders. Keywords: xUnit, Fact, Theory, integration tests, AppWebHostFactory, FluentClient, ProxyTimeProvider, TimeProvider, Foundatio.Xunit, TestWithLoggingBase, test data builders

316

dotnet-dev

GitTools

Expert guidance for .NET development in this repository. Use this skill for building, testing, debugging, and understanding project structure, coding conventions, dependency injection patterns, and testing practices.

11

quality-ci

managedcode

Set up or refine open-source .NET code-quality gates for CI: formatting, `.editorconfig`, SDK analyzers, third-party analyzers, coverage, mutation testing, architecture tests, and security scanning. USE FOR: .NET quality gates in CI; analyzer, coverage, mutation, and architecture-test choices; stand

00

Search skills

Search the agent skills registry