AutoFixture and NSubstitute integration guide — implementing auto-mocking. Use when you need to automatically create mock objects and simplify tests that involve complex dependency injection. Covers AutoNSubstituteDataAttribute, the Frozen mechanism, and the Greedy constructor strategy. Includes custom handling for special dependencies such as IMapper (AutoMapper/Mapster). Keywords: autofixture nsubstitute, auto mocking, AutoNSubstituteDataAttribute, auto-mocking, Frozen, AutoNSubstituteCustomization, AutoFixture.AutoNSubstitute, Greedy, fixture.Freeze, Received(), Returns(), IMapper, AutoMapper, Mapster, mapper testing
Substitute.For<T>()# 核心套件
dotnet add package AutoFixture.AutoNSubstitute
# 相關套件(如尚未安裝)
dotnet add package AutoFixture
dotnet add package AutoFixture.Xunit2
dotnet add package NSubstitute
dotnet add package xunit
| 套件名稱 | 用途 | NuGet 連結 |
| ----------------------------- | ------------------------------- | ------------------------------------------------------------------------ |
| AutoFixture.AutoNSubstitute | AutoFixture 與 NSubstitute 整合 | nuget.org |
| AutoFixture.Xunit2 | xUnit 整合(AutoData 屬性) | nuget.org |
| NSubstitute | 模擬框架 | nuget.org |
當在 AutoFixture 中加入 AutoNSubstituteCustomization 時,它會自動:
Substitute.For<T>() 建立 Mock 物件using AutoFixture;
using AutoFixture.AutoNSubstitute;
// 建立包含 AutoNSubstitute 功能的 Fixture
var fixture = new Fixture().Customize(new AutoNSubstituteCustomization());
// 自動建立服務和其相依性
// MyService 的所有介面相依性都會自動變成 NSubstitute 的替身
var service = fixture.Create<MyService>();
[Frozen] 屬性用來控制測試中某個類型的實例:
[Frozen] 時,AutoFixture 會建立這個類別的一個實例並凍結它[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) // 太晚凍結了
[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>() 呼叫[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();
}
優勢:
在實際專案中,通常需要整合多種客製化設定:
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;
}
}
用於結合固定測試值與自動產生物件:
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 繼承自 CompositeDataAttributeAutoDataAttribute 實例作為資料來源提供者AutoDataWithCustomizationAttribute 的所有設定某些相依性(如 IMapper)不適合使用 Mock,而應該使用真實實例。包含 Mapster 和 AutoMapper 的客製化範例。
完整客製化處理範例請參考 references/dependency-customization.md
涵蓋基本測試、Frozen 相依行為設定、自動產生測試資料、InlineAutoData 參數化測試、CollectionSize 控制、IFixture 複雜資料設定、Nullable 參考類型處理等完整範例。
完整測試實作範例請參考 references/test-implementation-examples.md
| 場景 | 原因 | | ---------------- | ---------------------------------- | | 服務層測試 | 通常有多個相依性,自動模擬效益最大 | | 複雜相依圖 | AutoFixture 自動處理多層相依性 | | 參數化測試 | 結合固定值與自動產生資料 | | 需要大量測試資料 | 減少手動建立測試資料的工作 | | 快速迭代開發 | 建構函式變更時測試通常不需修改 |
| 場景 | 原因 |
| ---------------------- | ---------------------------------------- |
| 單一相依性測試 | 手動建立可能更清晰直覺 |
| 精確控制屬性值 | 需要額外的 fixture.Build().With() 設定 |
| 團隊不熟悉 AutoFixture | 學習成本可能影響開發效率 |
| 除錯困難的場景 | 自動產生的物件可能讓除錯變複雜 |
| 效能敏感的測試 | 物件建立的開銷可能影響執行速度 |
漸進式採用
團隊培訓
建立規範
MyProject.Tests/
├── AutoFixtureConfigurations/
│ ├── AutoDataWithCustomizationAttribute.cs
│ ├── InlineAutoDataWithCustomizationAttribute.cs
│ ├── AutoMapperCustomization.cs
│ └── DomainCustomization.cs
├── Services/
│ ├── OrderServiceTests.cs
│ └── ShipperServiceTests.cs
└── ...
[專案名稱]AutoDataAttribute 或 AutoDataWithCustomizationAttribute[功能]Customization(如 MapsterMapperCustomization)方法_情境_預期 的命名模式參數順序錯誤
// Frozen 參數在 SUT 之後,不會生效
public void Test(MyService sut, [Frozen] IRepository repo)
// Frozen 參數必須在 SUT 之前
public void Test([Frozen] IRepository repo, MyService sut)
遺忘 AutoNSubstituteCustomization
// 沒有 AutoNSubstitute,介面會產生異常
var fixture = new Fixture();
// 加入 AutoNSubstituteCustomization
var fixture = new Fixture().Customize(new AutoNSubstituteCustomization());
過度依賴自動產生
// 測試意圖不明確
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);
}
[ClassData] 或 IClassFixture<T> 共享設定| 技能名稱 | 關聯說明 |
| ---------------------------- | -------------------------------------- |
| autofixture-basics | AutoFixture 基礎使用,本技能的前置知識 |
| autofixture-customization | 自訂 Customization 的進階用法 |
| autodata-xunit-integration | AutoData 屬性家族的完整說明 |
| nsubstitute-mocking | NSubstitute 基礎,Mock 設定的詳細說明 |
AutoDataAttribute 衍生類別檔案(*AutoDataAttribute.cs)ICustomization 實作類別檔案(*Customization.cs)[Theory] 搭配自訂 AutoData 屬性[Frozen] 參數置於 SUT 參數之前Returns() 與 Received() 進行行為設定與驗證本技能內容提煉自「老派軟體工程師的測試修練 - 30 天挑戰」系列文章:
npx skills add kevintsengtw/dotnet 测试 - AutoFixture 与 NSubstitute 集成下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer