dotnet-unit-testing
Generates unit tests for command and query handlers using xUnit and NSubstitute. Implements Arrange-Act-Assert pattern with comprehensive test coverage for success and failure scenarios.
git clone --depth 1 https://github.com/ronnythedev/dotnet-clean-architecture-skills /tmp/dotnet-unit-testing && cp -r /tmp/dotnet-unit-testing/skills/21-dotnet-unit-testing ~/.claude/skills/dotnet-unit-testingSKILL.md
# Unit Test Generator
## Overview
Unit tests for Clean Architecture handlers:
- **xUnit** - Test framework
- **NSubstitute** - Mocking library
- **FluentAssertions** - Readable assertions
- **AAA pattern** - Arrange, Act, Assert
## Quick Reference
| Test Type | Purpose | Example |
|-----------|---------|---------|
| Success test | Verify happy path | `Should_ReturnSuccess_When_ValidRequest` |
| Failure test | Verify error handling | `Should_ReturnFailure_When_NotFound` |
| Validation test | Verify input validation | `Should_ReturnValidationError_When_EmptyName` |
| Behavior test | Verify side effects | `Should_CallRepository_When_ValidRequest` |
---
## Test Project Structure
```
tests/
└── {name}.Application.UnitTests/
├── {Feature}/
│ ├── Create{Entity}/
│ │ ├── Create{Entity}CommandHandlerTests.cs
│ │ └── Create{Entity}CommandValidatorTests.cs
│ └── Get{Entity}ById/
│ └── Get{Entity}ByIdQueryHandlerTests.cs
├── Abstractions/
│ └── BaseTest.cs
└── {name}.Application.UnitTests.csproj
```
---
## Template: Test Project File
```xml
<!-- tests/{name}.Application.UnitTests/{name}.Application.UnitTests.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.16">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit" Version="2.6.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\{name}.application\{name}.application.csproj" />
<ProjectReference Include="..\..\src\{name}.domain\{name}.domain.csproj" />
</ItemGroup>
</Project>
```
---
## Template: Base Test Class
```csharp
// tests/{name}.Application.UnitTests/Abstractions/BaseTest.cs
using NSubstitute;
using {name}.domain.abstractions;
namespace {name}.Application.UnitTests.Abstractions;
public abstract class BaseTest
{
protected static CancellationToken CancellationToken => CancellationToken.None;
/// <summary>
/// Creates a mock that returns the provided result
/// </summary>
protected static T CreateMock<T>() where T : class
{
return Substitute.For<T>();
}
/// <summary>
/// Helper to create a successful Result
/// </summary>
protected static Result<T> SuccessResult<T>(T value)
{
return Result.Success(value);
}
/// <summary>
/// Helper to create a failed Result
/// </summary>
protected static Result<T> FailureResult<T>(Error error)
{
return Result.Failure<T>(error);
}
}
```
---
## Template: Command Handler Tests
```csharp
// tests/{name}.Application.UnitTests/{Feature}/Create{Entity}/Create{Entity}CommandHandlerTests.cs
using FluentAssertions;
using NSubstitute;
using {name}.application.{feature}.Create{Entity};
using {name}.domain.{aggregate};
using {name}.domain.abstractions;
using {name}.Application.UnitTests.Abstractions;
namespace {name}.Application.UnitTests.{Feature}.Create{Entity};
public sealed class Create{Entity}CommandHandlerTests : BaseTest
{
private readonly I{Entity}Repository _{entity}Repository;
private readonly IUnitOfWork _unitOfWork;
private readonly Create{Entity}CommandHandler _handler;
public Create{Entity}CommandHandlerTests()
{
// Arrange - Setup mocks (runs before each test)
_{entity}Repository = CreateMock<I{Entity}Repository>();
_unitOfWork = CreateMock<IUnitOfWork>();
_handler = new Create{Entity}CommandHandler(
_{entity}Repository,
_unitOfWork);
}
// ═══════════════════════════════════════════════════════════════
// SUCCESS TESTS
// ═══════════════════════════════════════════════════════════════
[Fact]
public async Task Handle_Should_ReturnSuccess_When_ValidRequest()
{
// Arrange
var command = new Create{Entity}Command(
Name: "Test Entity",
Description: "Test Description",
OrganizationId: Guid.NewGuid());
_{entity}Repository
.GetByNameAsync(command.Name, CancellationToken)
.Returns((Domain.{Aggregate}.{Entity}?)null);
// Act
var result = await _handler.Handle(command, CancellationToken);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeEmpty();
}
[Fact]
public async Task Handle_Should_AddEntity_When_ValidRequest()
{
// Arrange
var command = new Create{Entity}Command(
Name: "Test Entity",
Description: "Test Description",
OrganizationId: Guid.NewGuid());
_{entity}Repository
.GetByNameAsync(command.Name, CancellationToken)
.Returns((Domain.{Aggregate}.{Entity}?)null);
// Act
await _handler.Handle(command, CancellationToken);
// Assert
_{entity}Repository
.Received(1)
.Add(Arg.Is<Domain.{Aggregate}.{Entity}>(e =>Scaffolds a complete .NET solution following Clean Architecture principles with proper layer separation (API, Application, Domain, Infrastructure). Creates project structure, dependency injection setup, and cross-cutting concerns configuration.
Generates CQRS Commands with Handlers, Validators, and Request DTOs following Clean Architecture patterns. Commands represent actions that modify state and return Result types for proper error handling.
Generates CQRS Queries with Handlers and Response DTOs for read operations. Uses Dapper for optimized read queries, bypassing the domain model for better performance.
Generates Domain Entities following DDD principles with factory methods, private setters, domain events, and proper encapsulation. Supports aggregate roots, child entities, and value objects.
Generates Repository interfaces and implementations following the Repository pattern. Provides data access abstraction for aggregate roots with EF Core implementations.
Generates Entity Framework Core configurations using Fluent API. Maps domain entities to database tables with proper relationships, constraints, and conventions.
Generates RESTful API Controllers with proper routing, versioning, authorization, and MediatR integration. Follows REST conventions and Clean Architecture patterns.
Generates Minimal API endpoints following Microsoft's recommended approach. Creates fast, testable HTTP APIs with minimal code using MapGet/MapPost/MapPut/MapDelete. Preferred over controller-based APIs for new projects.