dotnet-testing-autofixture-nsubstitute-integration

AutoFixture 與 NSubstitute 整合指南 - 實現自動模擬 (Auto-Mocking)。當需要自動建立 Mock 物件、簡化複雜相依性注入測試時使用。涵蓋 AutoNSubstituteDataAttribute、Frozen 機制、Greedy 建構策略。包含 IMapper (AutoMapper/Mapster) 等特殊相依性的客製化處理。 Make sure to use this skill whenever the user mentions AutoFixture with NSubstitute, auto-mocking, AutoNSubstituteCustomization, Frozen attribute, or AutoNSubstituteDataAttribute, even if they don't explicitly ask for integration guidance. Keywords: autofixture nsubstitute, auto mocking, AutoNSubstituteDataAttribute, 自動模擬, Frozen, AutoNSubstituteCustomization, AutoFixture.AutoNSubstitute, Greedy, fixture.Freeze, Received(), Returns(), IMapper, AutoMapper, Mapster, mapper testing

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "dotnet-testing-autofixture-nsubstitute-integration" with this command: npx skills add kevintsengtw/dotnet-testing-agent-skills/kevintsengtw-dotnet-testing-agent-skills-dotnet-testing-autofixture-nsubstitute-integration

AutoFixture + NSubstitute 自動模擬整合

核心價值

  • 減少樣板程式碼:不需要手動為每個介面建立 Substitute.For<T>()
  • 自動處理複雜相依圖:AutoFixture 會自動解析並建立所需的物件
  • 提升測試維護性:當建構函式變更時,測試程式碼通常不需要同步修改
  • 保持測試重點:讓開發者專注於測試邏輯而非物件建立

套件安裝與設定

必要套件

# 核心套件
dotnet add package AutoFixture.AutoNSubstitute

# 相關套件(如尚未安裝)
dotnet add package AutoFixture
dotnet add package AutoFixture.Xunit2
dotnet add package NSubstitute
dotnet add package xunit

NuGet 套件資訊

套件名稱用途NuGet 連結
AutoFixture.AutoNSubstituteAutoFixture 與 NSubstitute 整合nuget.org
AutoFixture.Xunit2xUnit 整合(AutoData 屬性)nuget.org
NSubstitute模擬框架nuget.org

核心概念

AutoNSubstituteCustomization 的作用

當在 AutoFixture 中加入 AutoNSubstituteCustomization 時,它會自動:

  1. 偵測介面類型:當 AutoFixture 遇到介面或抽象類別時
  2. 自動建立替身:使用 NSubstitute 的 Substitute.For<T>() 建立 Mock 物件
  3. 注入相依性:將這些替身物件注入到需要的建構函式中
  4. 保持實例一致性:確保相同類型的替身在同一個測試中保持一致
using AutoFixture;
using AutoFixture.AutoNSubstitute;

// 建立包含 AutoNSubstitute 功能的 Fixture
var fixture = new Fixture().Customize(new AutoNSubstituteCustomization());

// 自動建立服務和其相依性
// MyService 的所有介面相依性都會自動變成 NSubstitute 的替身
var service = fixture.Create<MyService>();

FrozenAttribute 凍結機制

[Frozen] 屬性用來控制測試中某個類型的實例:

  • 當參數被標註為 [Frozen] 時,AutoFixture 會建立這個類別的一個實例並凍結
  • 後續在測試方法中都會使用同一個已凍結的實例
  • 這對於需要設定相依性行為然後驗證 SUT 的測試特別重要
[Theory]
[AutoData]
public async Task TestMethod(
    [Frozen] IRepository repository,  // 這個 repository 會被凍結
    MyService sut)                    // sut 會使用同一個 repository
{
    // 設定凍結實例的行為
    repository.GetAsync(Arg.Any<int>()).Returns(someData);

    // SUT 內部使用的是同一個 repository 實例
    var result = await sut.DoSomething();
}

參數順序的重要性

使用 [Frozen] 時,參數順序非常重要

// 正確:Frozen 參數在 SUT 之前
public async Task TestMethod(
    [Frozen] IRepository repository,
    MyService sut)

// 錯誤:SUT 會使用不同的 repository 實例
public async Task TestMethod(
    MyService sut,
    [Frozen] IRepository repository)  // 太晚凍結了

傳統方式 vs AutoNSubstitute 方式

傳統手動方式

[Fact]
public async Task TraditionalWay()
{
    // Arrange - 手動建立每個相依性
    var repository = Substitute.For<IRepository>();
    var logger = Substitute.For<ILogger<OrderService>>();
    var notificationService = Substitute.For<INotificationService>();
    var cacheService = Substitute.For<ICacheService>();

    var sut = new OrderService(repository, logger, notificationService, cacheService);

    // 設定替身行為
    repository.GetOrderAsync(Arg.Any<int>()).Returns(someOrder);

    // Act
    var result = await sut.GetOrderAsync(orderId);

    // Assert
    result.Should().NotBeNull();
}

問題

  • 當服務增加新相依性時,所有測試都需要修改
  • 大量重複的 Substitute.For<T>() 呼叫
  • 測試程式碼冗長,難以快速理解測試意圖

使用 AutoNSubstitute 方式

[Theory]
[AutoDataWithCustomization]
public async Task WithAutoNSubstitute(
    [Frozen] IRepository repository,
    OrderService sut)
{
    // Arrange - 相依性已自動建立,只需設定需要的行為
    repository.GetOrderAsync(Arg.Any<int>()).Returns(someOrder);

    // Act
    var result = await sut.GetOrderAsync(orderId);

    // Assert
    result.Should().NotBeNull();
}

優勢

  • 只需宣告需要互動的相依性
  • 其他相依性(logger, notificationService, cacheService)自動建立
  • 建構函式變更時,測試通常不需要修改

自訂 AutoData 屬性

為什麼需要自訂 AutoData 屬性?

在實際專案中,通常需要整合多種客製化設定:

  • AutoNSubstituteCustomization:自動為介面建立 NSubstitute 替身
  • 專案特定的 Customization:如 Mapper 設定、驗證器設定等
  • 一致的測試基礎設施:確保整個專案使用相同的設定

AutoDataWithCustomizationAttribute 實作

using AutoFixture;
using AutoFixture.AutoNSubstitute;
using AutoFixture.Xunit2;

namespace MyProject.Tests.AutoFixtureConfigurations;

/// <summary>
/// 包含客製化設定的 AutoData 屬性
/// </summary>
public class AutoDataWithCustomizationAttribute : AutoDataAttribute
{
    /// <summary>
    /// 建構函式
    /// </summary>
    public AutoDataWithCustomizationAttribute() : base(CreateFixture)
    {
    }

    private static IFixture CreateFixture()
    {
        var fixture = new Fixture()
            .Customize(new AutoNSubstituteCustomization())
            .Customize(new MapsterMapperCustomization())  // 專案特定設定
            .Customize(new DomainCustomization());        // 領域模型設定

        return fixture;
    }
}

InlineAutoDataWithCustomizationAttribute 實作

用於結合固定測試值與自動產生物件:

using AutoFixture;
using AutoFixture.AutoNSubstitute;
using AutoFixture.Xunit2;

namespace MyProject.Tests.AutoFixtureConfigurations;

/// <summary>
/// 包含客製化設定的 InlineAutoData 屬性
/// </summary>
public class InlineAutoDataWithCustomizationAttribute : InlineAutoDataAttribute
{
    /// <summary>
    /// 建構函式
    /// </summary>
    /// <param name="values">固定值(將填入測試方法的前幾個參數)</param>
    public InlineAutoDataWithCustomizationAttribute(params object[] values)
        : base(new AutoDataWithCustomizationAttribute(), values)
    {
    }
}

重要實作細節

為什麼使用 new AutoDataWithCustomizationAttribute() 而不是 CreateFixture 方法?

// 錯誤:InlineAutoDataAttribute 需要 AutoDataAttribute,不是 Func<IFixture>
public InlineAutoDataWithCustomizationAttribute(params object[] values)
    : base(CreateFixture, values)  // 編譯錯誤或行為異常

// 正確:傳遞 AutoDataAttribute 實例
public InlineAutoDataWithCustomizationAttribute(params object[] values)
    : base(new AutoDataWithCustomizationAttribute(), values)

原因:

  • InlineAutoDataAttribute 繼承自 CompositeDataAttribute
  • 它需要接收一個 AutoDataAttribute 實例作為資料來源提供者
  • 這樣可以重用 AutoDataWithCustomizationAttribute 的所有設定

常見相依性的客製化處理

某些相依性(如 IMapper)不適合使用 Mock,而應該使用真實實例。包含 Mapster 和 AutoMapper 的客製化範例。

完整客製化處理範例請參考 references/dependency-customization.md


測試實作範例

涵蓋基本測試、Frozen 相依行為設定、自動產生測試資料、InlineAutoData 參數化測試、CollectionSize 控制、IFixture 複雜資料設定、Nullable 參考類型處理等完整範例。

完整測試實作範例請參考 references/test-implementation-examples.md


適用場景判斷

建議使用的場景

場景原因
服務層測試通常有多個相依性,自動模擬效益最大
複雜相依圖AutoFixture 自動處理多層相依性
參數化測試結合固定值與自動產生資料
需要大量測試資料減少手動建立測試資料的工作
快速迭代開發建構函式變更時測試通常不需修改

謹慎使用的場景

場景原因
單一相依性測試手動建立可能更清晰直覺
精確控制屬性值需要額外的 fixture.Build().With() 設定
團隊不熟悉 AutoFixture學習成本可能影響開發效率
除錯困難的場景自動產生的物件可能讓除錯變複雜
效能敏感的測試物件建立的開銷可能影響執行速度

最佳實踐

導入策略

  1. 漸進式採用

    • 從簡單的服務類別開始
    • 逐步擴展到複雜場景
    • 讓團隊逐漸熟悉模式
  2. 團隊培訓

    • 確保團隊理解 Frozen 機制
    • 說明參數順序的重要性
    • 分享除錯技巧
  3. 建立規範

    • 何時使用自動產生 vs 手動建立
    • 自訂 Customization 的命名與組織
    • 測試資料的控制策略

程式碼組織

MyProject.Tests/
├── AutoFixtureConfigurations/
│   ├── AutoDataWithCustomizationAttribute.cs
│   ├── InlineAutoDataWithCustomizationAttribute.cs
│   ├── AutoMapperCustomization.cs
│   └── DomainCustomization.cs
├── Services/
│   ├── OrderServiceTests.cs
│   └── ShipperServiceTests.cs
└── ...

命名慣例

  • 自訂 AutoData 屬性[專案名稱]AutoDataAttributeAutoDataWithCustomizationAttribute
  • Customization 類別[功能]Customization(如 MapsterMapperCustomization
  • 測試方法:維持 方法_情境_預期 的命名模式

注意事項與限制

常見陷阱

  1. 參數順序錯誤

    // Frozen 參數在 SUT 之後,不會生效
    public void Test(MyService sut, [Frozen] IRepository repo)
    
    // Frozen 參數必須在 SUT 之前
    public void Test([Frozen] IRepository repo, MyService sut)
    
  2. 遺忘 AutoNSubstituteCustomization

    // 沒有 AutoNSubstitute,介面會產生異常
    var fixture = new Fixture();
    
    // 加入 AutoNSubstituteCustomization
    var fixture = new Fixture().Customize(new AutoNSubstituteCustomization());
    
  3. 過度依賴自動產生

    // 測試意圖不明確
    public void Test(Order order, Customer customer, MyService sut)
    {
        var result = sut.Process(order);
        result.Should().NotBeNull();  // 驗證什麼?
    }
    
    // 明確控制關鍵屬性
    public void Test(IFixture fixture, MyService sut)
    {
        var order = fixture.Build<Order>()
                           .With(o => o.Status, OrderStatus.Pending)
                           .Create();
    
        var result = sut.Process(order);
        result.Status.Should().Be(OrderStatus.Processed);
    }
    

效能考量

  • 每個測試方法都會建立新的 Fixture 和所有相依性
  • 複雜物件圖可能增加測試執行時間
  • 考慮使用 [ClassData]IClassFixture<T> 共享設定

相關技能

技能名稱關聯說明
autofixture-basicsAutoFixture 基礎使用,本技能的前置知識
autofixture-customization自訂 Customization 的進階用法
autodata-xunit-integrationAutoData 屬性家族的完整說明
nsubstitute-mockingNSubstitute 基礎,Mock 設定的詳細說明

輸出格式

  • 產生自訂 AutoDataAttribute 衍生類別檔案(*AutoDataAttribute.cs
  • 產生 ICustomization 實作類別檔案(*Customization.cs
  • 測試方法使用 [Theory] 搭配自訂 AutoData 屬性
  • [Frozen] 參數置於 SUT 參數之前
  • 搭配 NSubstitute 的 Returns()Received() 進行行為設定與驗證

參考資源

原始文章

本技能內容提煉自「老派軟體工程師的測試修練 - 30 天挑戰」系列文章:

官方文件

延伸閱讀

範例程式碼

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

dotnet-testing-advanced-webapi-integration-testing

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

dotnet-testing-unit-test-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

dotnet-testing-xunit-project-setup

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

dotnet-testing-advanced-aspnet-integration-testing

No summary provided by upstream source.

Repository SourceNeeds Review