Claude
Skills
Sign in
Back

bdd-dotnet

Included with Lifetime
$97 forever

# .NET Unit Testing Skill

General

What this skill does

# .NET Unit Testing Skill

Use this skill when writing unit tests for the Domain.Shared project. This skill captures the unique testing patterns, conventions, and philosophy used in this codebase.

## Testing Philosophy

### Core Principles
- **Ports Testing Only**: Unit tests focus exclusively on testing domain ports (handlers). Each handler represents one user story or use case.
- **Hybrid Testing Approach**: We use real repository implementations with EF Core InMemory database instead of mocks. This "shifts left" to catch issues earlier with more coverage for less effort.
- **No Mock Libraries**: Instead of using mocking frameworks, we create fake implementations for dependencies with realistic behavior.
- **Realistic Behaviors**: Tests cover realistic use cases and behaviors, not artificial scenarios.
- **TDD Support**: The pattern supports Test-Driven Development - start from the test and model, then write ports/handlers and even entity data configurations from tests.

### What to Test
- Handler behavior (command handlers and query handlers)
- Business logic in domain aggregates
- Repository interactions through real implementations
- Workflow and state transitions
- Validation and error handling
- Edge cases and boundary conditions

### What NOT to Test
- Infrastructure implementation details
- Database queries directly
- Third-party library behavior
- Framework features

## Project Structure

### Test Project Configuration
```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <!-- NUnit Testing Framework -->
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0"/>
    <PackageReference Include="NUnit" Version="3.13.3"/>
    <PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
    <PackageReference Include="NUnit.Analyzers" Version="3.3.0"/>

    <!-- InMemory Database for Testing -->
    <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.32" />
  </ItemGroup>
</Project>
```

### Directory Structure
```
RMS.Domain.Tests/
├── Builders/
│   ├── TestContextFactory.cs      # Creates test database context
│   ├── TestDataBuilder.cs         # Main test data builder with repositories
│   ├── ProductBuilder.cs          # Fluent builder for Product aggregate
│   ├── FamilyBuilder.cs     # Fluent builder for Family aggregate
│   └── [Other aggregate builders]
├── Fakes/
│   ├── FakeClock.cs               # Fake time service
│   ├── FakeUnitOfWork.cs          # Fake transaction coordinator
│   ├── FakeBlobStorageService.cs  # Fake blob storage
│   └── [Other fake services]
├── Product/
│   ├── SetFamilyToProductCommandHandlerTests.cs
│   └── [Other product handler tests]
├── Competitive/
│   └── [Competitive agreement handler tests]
└── TestData/
    └── [CSV files for test data]
```

## Core Testing Components

### 1. TestContextFactory

Creates an EF Core InMemory database context for testing.

```csharp
public class TestContextFactory
{
    private readonly DbContextType _contextType;
    private readonly string _inMemoryDatabaseName;

    public TestContextFactory(
        DbContextType contextType = DbContextType.InMemory,
        string? inMemoryDatabaseName = null)
    {
        _contextType = contextType;
        _inMemoryDatabaseName = inMemoryDatabaseName ?? Guid.NewGuid().ToString();
    }

    public MyContext Context => CreateContext(_contextType, _inMemoryDatabaseName);
}
```

**Key Points:**
- Each test gets a unique in-memory database by default
- Can optionally test against LocalSqlServer for integration scenarios
- Database is created automatically via `EnsureCreated()`

### 2. TestDataBuilder

The main orchestrator for test setup. It:
- Creates and holds real repository implementations
- Manages the DbContext lifecycle
- Provides fluent methods to add test data
- Registers services in a ServiceCollection

**Example from SetFamilyToProductCommandHandlerTests.cs:**
```csharp
[SetUp]
public void SetUp()
{
    _clock = new FakeClock();
    _testDataBuilder = new TestDataBuilder();

    _handler = new SetFamilyToProductCommandHandler(
        _clock,
        _testDataBuilder.ProductRepository,  // Real repository!
        new NullLogger<SetFamilyToProductCommandHandler>(),
        _testDataBuilder.FamilyRepository,
        new FakeUnitOfWork());
}

[Test]
public async Task GivenSetProductToFamilyRequest_WhenHandled_ThenFamilyAndProductAreUpdated()
{
    // arrange
    var Family = new FamilyBuilder().Build();
    var productNo = "1234";
    var product = new ProductBuilder(productNo, "5678").Build();

    _testDataBuilder
        .WithFamily(Family)
        .WithProduct(product);

    var command = new SetFamilyToProductCommand
    {
        ProductNo = productNo,
        FamilyId = Family.Id,
        Type = SetFamilyToProductType.SetFamilyCode,
        UserId = "userId"
    };

    // act
    var result = await _handler.HandleAsync(command, CancellationToken.None);

    // assert
    Assert.That(result.Success, Is.True, "Command should be successful");

    var updatedProduct = await _testDataBuilder.ProductQueryRepository
        .GetAsync(productNo, CancellationToken.None);
    Assert.IsNotNull(updatedProduct, "Product should be found");
    Assert.That(updatedProduct.FamilyId, Is.EqualTo(Family.Id));
}
```

**TestDataBuilder Key Methods:**
```csharp
// Setup methods (fluent API)
.WithProduct(product)
.WithFamily(Family)
.WithAuthorizedProduct(authorizedProduct)
.WithDistributorSite(distributorSite)
.WithImportProductRequest(request)
.WithCompetitiveAgreement(agreement)

// Retrieval methods
.GetAll<T>()          // Get all entities of type T
.CountAll<T>()        // Count entities of type T

// Repository access
.ProductRepository
.ProductQueryRepository
.FamilyRepository
.CompetitiveAgreementRepository
// ... many more
```

### 3. Aggregate Builders (Fluent API)

Builders use the fluent pattern to construct domain aggregates with test data.

**ProductBuilder Example:**
```csharp
public class ProductBuilder
{
    private readonly string _productNo;
    private readonly string? _productClassNo;
    private decimal? _listPrice;
    private DateTime? _validFrom;
    private string? _scaleType;

    public ProductBuilder(string productNo, string productClassNo)
    {
        _productNo = productNo;
        _productClassNo = productClassNo;
    }

    public ProductBuilder WithListPrice(decimal listPrice)
    {
        _listPrice = listPrice;
        return this;
    }

    public ProductBuilder WithValidFrom(DateTime validFrom)
    {
        _validFrom = validFrom;
        return this;
    }

    public ProductBuilder WithScaleType(string scaleType)
    {
        _scaleType = scaleType;
        return this;
    }

    public Product Build()
    {
        var clock = new FakeClock();
        return new Product(
            _productNo,
            _listPrice,
            _validFrom ?? clock.UtcNow(),
            _productFamilyNo ?? "fam",
            null,
            _scaleType ?? "MOTORS",
            _productClassNo,
            // ... other parameters
        );
    }
}

// Usage in tests:
var product = new ProductBuilder("12345", "classNo")
    .WithListPrice(100.50m)
    .WithValidFrom(new DateTime(2024, 1, 1))
    .WithScaleType("INDUSTRIAL")
    .Build();
```


### 4. Fake Implementations

Create simple, controllable fake implementations instead of using mocking frameworks.

**FakeClock (Control Time):**
```csharp
public class FakeClock : IClock
{
    private DateTime _utcNow;

    public FakeClock(DateTime? utcNow = null)
    {
        _utcNow = utcNow ?? DateTime.UtcNow;
    }

    public DateTime UtcNow() => _utcNow;

    public DateTimeOffset ZeroOffsetUtcNow() => _utcNow;

    public void OverrideUtcNow(DateTime utcNow)
    {
        _utcNow = utcNow;
    }
}

// Usage:
var clock = new FakeClock(new DateTime(2024, 1, 1)
Files: 3
Size: 30.4 KB
Complexity: 34/100
Category: General

Related in General