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:
- Slow - Need actual database operations, file systems, networks
- Unstable - External service failures cause test failures
- Difficult to Repeat - Time, random numbers cause inconsistent results
- Environment Dependent - Need specific external environment setup
- 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("john@example.com", "password123");
capturedUser.Should().NotBeNull();
capturedUser.Email.Should().Be("john@example.com");
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 = "john@example.com" };
// Wrong: meaningless data
var user = new User { Id = 1, Name = "test", Email = "a@b.c" };
```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("test data");
```text
### Q2: How to verify method call order?
**A:** Use Received.InOrder():
```csharp
Received.InOrder(() =>
{
_service.Start();
_service.Process();
_service.Stop();
});
```text
### Q3: How to handle out parameters?
**A:** Use Returns() with delegate:
```csharp
_service.TryGetValue("key", out Arg.Any<string>())
.Returns(x =>
{
x[1] = "value";
return true;
});
```text
### Q4: NSubstitute vs Moq which to choose?
**A:** NSubstitute advantages:
- More concise and intuitive syntax
- Gentle learning curve
- No privacy controversies
- Sufficient for most testing scenarios
Choose NSubstitute, unless:
- Project already uses Moq
- Need Moq-specific advanced features
- Team already familiar with Moq syntax
## Integration with Other Skills
This skill can be combined with the following skills:
- **unit-test-fundamentals**: Unit test fundamentals and 3A pattern
- **dependency-injection-testing**: Dependency injection testing strategies
- **test-naming-conventions**: Test naming conventions
- **test-output-logging**: ITestOutputHelper and ILogger integration
- **datetime-testing-timeprovider**: TimeProvider abstraction for time dependencies
- **filesystem-testing-abstractions**: File system dependency abstraction
## Template Files Reference
This skill provides the following template files:
- `templates/mock-patterns.cs`: Complete Mock/Stub/Spy pattern examples
- `templates/verification-examples.cs`: Behavior verification and argument matching examples
- `references/practical-patterns.md`: Five practical patterns with complete code
- `references/test-double-types.md`: Test Double five types detailed examples
## Reference Resources
### Original Articles
This skill content is extracted from "Old School Software Engineer's Testing Practice - 30 Day Challenge" series:
- **Day 07 - Dependency Substitution Entry: Using NSubstitute**
- Ironman Article: https://ithelp.ithome.com.tw/articles/10374593
- Sample Code: https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day07
### NSubstitute Official
- [NSubstitute Official Website](https://nsubstitute.github.io/)
- [NSubstitute GitHub](https://github.com/nsubstitute/NSubstitute)
- [NSubstitute NuGet](https://www.nuget.org/packages/NSubstitute/)
### Test Double Theory
- [XUnit Test Patterns](http://xunitpatterns.com/Test%20Double.html)
- [Martin Fowler - Test Double](https://martinfowler.com/bliki/TestDouble.html)
1---2name: dotnet-testing-nsubstitute-mocking3category: testing4subcategory: mocking5description: |6 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.7 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 testing8targets: ['*']9license: MIT10metadata:11 author: Kevin Tseng12 version: '1.0.0'13 tags: '.NET, testing, NSubstitute, mock, stub, test double'14 related_skills: 'autofixture-nsubstitute-integration, unit-test-fundamentals, private-internal-testing'15claudecode: {}16opencode: {}17codexcli:18 short-description: '.NET skill guidance for dotnet-testing-nsubstitute-mocking'19copilot: {}20geminicli: {}21antigravity: {}22---2324Source: kevintsengtw/dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.2526# NSubstitute Test Double Guide2728## Applicable Scenarios2930This skill focuses on creating and managing test doubles using NSubstitute, covering the five types of Test Doubles,31dependency isolation strategies, behavior setup and validation best practices.3233## Why Do We Need Test Doubles?3435Real-world code usually depends on external resources, which make tests:36371. **Slow** - Need actual database operations, file systems, networks382. **Unstable** - External service failures cause test failures393. **Difficult to Repeat** - Time, random numbers cause inconsistent results404. **Environment Dependent** - Need specific external environment setup415. **Development Blocking** - Must wait for external systems to be ready4243Test doubles allow us to isolate these dependencies and focus on testing business logic.4445## Prerequisites4647### Package Installation4849````xml50<PackageReference Include="NSubstitute" Version="5.3.0" />51<PackageReference Include="xunit" Version="2.9.3" />52<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />53<PackageReference Include="AwesomeAssertions" Version="9.1.0" />54```text5556### Basic using Directives5758```csharp59using NSubstitute;60using NSubstitute.ExceptionExtensions;61using Xunit;62using AwesomeAssertions;63using Microsoft.Extensions.Logging;64```text6566## Test Double Five Types6768According to Gerard Meszaros's definition in "xUnit Test Patterns", test doubles are divided into five types:6970| Type | Purpose | NSubstitute Equivalent |71|------|------|-------------------|72| **Dummy** | Fill objects, only satisfy method signatures | `Substitute.For<T>()` without setting any behavior |73| **Stub** | Provide default return values, set test scenarios | `.Returns(value)` |74| **Fake** | Simplified implementation with real logic | Manual interface implementation (like `FakeUserRepository`) |75| **Spy** | Record calls, verify afterwards | `.Received()` validation |76| **Mock** | Default expected interactions, test fails if not satisfied | `.Received(n)` strict validation |7778> Full code examples for each type please refer to [references/test-double-types.md](references/test-double-types.md)7980## NSubstitute Core Functions8182### Basic Substitution Syntax8384```csharp85// Create interface substitute86var substitute = Substitute.For<IUserRepository>();8788// Create class substitute (needs virtual members)89var classSubstitute = Substitute.For<BaseService>();9091// Create multiple interface substitute92var multiSubstitute = Substitute.For<IService, IDisposable>();93```text9495### Return Value Setup9697#### Basic Return Values9899```csharp100// Exact parameter matching101_repository.GetById(1).Returns(new User { Id = 1, Name = "John" });102103// Any parameter matching104_service.Process(Arg.Any<string>()).Returns("processed");105106// Return sequence values107_generator.GetNext().Returns(1, 2, 3, 4, 5);108```text109110#### Conditional Return Values111112```csharp113// Use delegate to calculate return value114_calculator.Add(Arg.Any<int>(), Arg.Any<int>())115 .Returns(x => (int)x[0] + (int)x[1]);116117// Condition matching118_service.Process(Arg.Is<string>(x => x.StartsWith("test")))119 .Returns("test-result");120```text121122#### Throw Exceptions123124```csharp125// Synchronous method throws exception126_service.RiskyOperation()127 .Throws(new InvalidOperationException("Something went wrong"));128129// Async method throws exception130_service.RiskyOperationAsync()131 .Throws(new InvalidOperationException("Async operation failed"));132```text133134### Argument Matchers135136```csharp137// Any value138_service.Process(Arg.Any<string>()).Returns("result");139140// Specific condition141_service.Process(Arg.Is<string>(x => x.Length > 5)).Returns("long-result");142143// Argument capture144string capturedArg = null;145_service.Process(Arg.Do<string>(x => capturedArg = x)).Returns("result");146_service.Process("test");147capturedArg.Should().Be("test");148149// Argument check150_service.Process(Arg.Is<string>(x =>151{152 x.Should().StartWith("prefix");153 return true;154})).Returns("result");155```text156157### Call Verification158159```csharp160// Verify was called (at least once)161_service.Received().Process("test");162163// Verify call count164_service.Received(2).Process(Arg.Any<string>());165166// Verify was not called167_service.DidNotReceive().Delete(Arg.Any<int>());168169// Verify any argument call170_service.ReceivedWithAnyArgs().Process(default);171172// Verify call order173Received.InOrder(() =>174{175 _service.Start();176 _service.Process();177 _service.Stop();178});179```text180181## Practical Patterns182183Covers five common NSubstitute practical patterns, including complete code examples:184185| Pattern | Description |186|------|------|187| Pattern 1: Dependency Injection and Test Setup | FileBackupService complete example, including constructor injection and SUT setup |188| Pattern 2: Mock vs Stub Differences | Stub focuses on state return values vs Mock focuses on interaction behavior validation |189| Pattern 3: Async Method Testing | `Returns(Task.FromResult(...))` and `.Throws()` patterns |190| Pattern 4: ILogger Validation | Validate underlying `Log` method bypassing extension method limitations |191| Pattern 5: Complex Setup Management | Base test class managing shared Substitute setups |192193> Full code examples please refer to [references/practical-patterns.md](references/practical-patterns.md)194195## Advanced Argument Matching Techniques196197### Complex Object Matching198199```csharp200[Fact]201public void CreateOrder_CreateOrder_ShouldStoreCorrectOrderData()202{203 var repository = Substitute.For<IOrderRepository>();204 var service = new OrderService(repository);205206 service.CreateOrder("Product A", 5, 100);207208 // Verify object properties209 repository.Received(1).Save(Arg.Is<Order>(o =>210 o.ProductName == "Product A" &&211 o.Quantity == 5 &&212 o.Price == 100));213}214```text215216### Argument Capture and Validation217218```csharp219[Fact]220public void RegisterUser_RegisterUser_ShouldGenerateCorrectHashPassword()221{222 var repository = Substitute.For<IUserRepository>();223 var service = new UserService(repository);224225 User capturedUser = null;226 repository.Save(Arg.Do<User>(u => capturedUser = u));227228 service.RegisterUser("john@example.com", "password123");229230 capturedUser.Should().NotBeNull();231 capturedUser.Email.Should().Be("john@example.com");232 capturedUser.PasswordHash.Should().NotBe("password123"); // Should be hashed233 capturedUser.PasswordHash.Length.Should().BeGreaterThan(20);234}235```text236237## Common Pitfalls and Best Practices238239### Recommended Practices2402411. **Target interfaces rather than implementations for Substitutes**242243 ```csharp244 // Correct: target interface245 var repository = Substitute.For<IUserRepository>();246247 // Wrong: target concrete class (unless has virtual members)248 var repository = Substitute.For<UserRepository>();249```text2502512. **Use meaningful test data**252253 ```csharp254 // Correct: clearly express intent255 var user = new User { Id = 123, Name = "John Doe", Email = "john@example.com" };256257 // Wrong: meaningless data258 var user = new User { Id = 1, Name = "test", Email = "a@b.c" };259```text2602613. **Avoid over-verification**262263 ```csharp264 // Correct: only verify important behaviors265 _emailService.Received(1).SendWelcomeEmail(Arg.Any<string>());266267 // Wrong: verify all internal implementation details268 _repository.Received(1).GetById(123);269 _repository.Received(1).Update(Arg.Any<User>());270 _validator.Received(1).Validate(Arg.Any<User>());271```text2722734. **Clear distinction between Mock and Stub**274275 ```csharp276 // Correct: Stub for setting scenarios, Mock for validating behaviors277 var stubRepository = Substitute.For<IUserRepository>(); // Stub278 var mockLogger = Substitute.For<ILogger>(); // Mock279280 stubRepository.GetById(123).Returns(user);281 service.ProcessUser(123);282 mockLogger.Received(1).LogInformation(Arg.Any<string>());283```text284285### Practices to Avoid2862871. **Avoid simulating value types**288289 ```csharp290 // Wrong: DateTime is value type291 var badDate = Substitute.For<DateTime>();292293 // Correct: abstract time provider294 var dateTimeProvider = Substitute.For<IDateTimeProvider>();295 dateTimeProvider.Now.Returns(new DateTime(2024, 1, 1));296```text2972982. **Avoid tight coupling between tests and implementations**299300 ```csharp301 // Wrong: test implementation details302 _repository.Received(1).Query(Arg.Any<string>());303 _repository.Received(1).Filter(Arg.Any<Expression<Func<User, bool>>>());304305 // Correct: test behavior results306 var users = service.GetActiveUsers();307 users.Should().HaveCount(2);308```text3093103. **Avoid overly complex setups**311312 ```csharp313 // Wrong: too many Substitutes (may violate SRP)314 var sub1 = Substitute.For<IService1>();315 var sub2 = Substitute.For<IService2>();316 var sub3 = Substitute.For<IService3>();317 var sub4 = Substitute.For<IService4>();318319 // Correct: reconsider class responsibilities320 // Consider whether violating single responsibility principle, needs refactoring321```text322323## Identifying Dependencies to Substitute324325### Should Substitute326327- External API calls (IHttpClient, IApiClient)328- Database operations (IRepository, IDbContext)329- File system operations (IFileSystem)330- Network communication (IEmailService, IMessageQueue)331- Time dependencies (IDateTimeProvider, TimeProvider)332- Random number generation (IRandom)333- Expensive calculations (IComplexCalculator)334- Logging services (ILogger<T>)335336### Should Not Substitute337338- Value objects (DateTime, string, int)339- Simple data transfer objects (DTO)340- Pure function tools (like AutoMapper's IMapper, consider using real instance)341- Framework core classes (unless explicitly needed)342343## Troubleshooting344345### Q1: How to test classes without interfaces?346347**A:** Ensure members to be simulated are virtual:348349```csharp350public class BaseService351{352 public virtual string GetData() => "real data";353}354355var substitute = Substitute.For<BaseService>();356substitute.GetData().Returns("test data");357```text358359### Q2: How to verify method call order?360361**A:** Use Received.InOrder():362363```csharp364Received.InOrder(() =>365{366 _service.Start();367 _service.Process();368 _service.Stop();369});370```text371372### Q3: How to handle out parameters?373374**A:** Use Returns() with delegate:375376```csharp377_service.TryGetValue("key", out Arg.Any<string>())378 .Returns(x =>379 {380 x[1] = "value";381 return true;382 });383```text384385### Q4: NSubstitute vs Moq which to choose?386387**A:** NSubstitute advantages:388389- More concise and intuitive syntax390- Gentle learning curve391- No privacy controversies392- Sufficient for most testing scenarios393394Choose NSubstitute, unless:395396- Project already uses Moq397- Need Moq-specific advanced features398- Team already familiar with Moq syntax399400## Integration with Other Skills401402This skill can be combined with the following skills:403404- **unit-test-fundamentals**: Unit test fundamentals and 3A pattern405- **dependency-injection-testing**: Dependency injection testing strategies406- **test-naming-conventions**: Test naming conventions407- **test-output-logging**: ITestOutputHelper and ILogger integration408- **datetime-testing-timeprovider**: TimeProvider abstraction for time dependencies409- **filesystem-testing-abstractions**: File system dependency abstraction410411## Template Files Reference412413This skill provides the following template files:414415- `templates/mock-patterns.cs`: Complete Mock/Stub/Spy pattern examples416- `templates/verification-examples.cs`: Behavior verification and argument matching examples417- `references/practical-patterns.md`: Five practical patterns with complete code418- `references/test-double-types.md`: Test Double five types detailed examples419420## Reference Resources421422### Original Articles423424This skill content is extracted from "Old School Software Engineer's Testing Practice - 30 Day Challenge" series:425426- **Day 07 - Dependency Substitution Entry: Using NSubstitute**427 - Ironman Article: https://ithelp.ithome.com.tw/articles/10374593428 - Sample Code: https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day07429430### NSubstitute Official431432- [NSubstitute Official Website](https://nsubstitute.github.io/)433- [NSubstitute GitHub](https://github.com/nsubstitute/NSubstitute)434- [NSubstitute NuGet](https://www.nuget.org/packages/NSubstitute/)435436### Test Double Theory437438- [XUnit Test Patterns](http://xunitpatterns.com/Test%20Double.html)439- [Martin Fowler - Test Double](https://martinfowler.com/bliki/TestDouble.html)440````