SuperAPI AI Directory

dotnet-testing-autodata-xunit-integration

Data & analytics

View repository

Updated 10-15 23:45

About this skill

The AutoData attribute family: Integrated application of xUnit and AutoFixture

Overview

The AutoData attribute family is a feature provided by the AutoFixture.Xunit2 package that integrates AutoFixture's data generation capabilities with xUnit's parameterized tests, allowing test parameters to be automatically injected and greatly reducing test preparation code.

Core Features

  1. AutoData: Automatically generates all test parameters
  2. InlineAutoData: Mixes fixed values with automatic generation
  3. MemberAutoData: Combines external data sources
  4. CompositeAutoData: Integrates multiple data sources
  5. CollectionSizeAttribute: Controls the number of collection items generated

Installing the Package

<PackageReference Include="AutoFixture" Version="4.18.1" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.18.1" />
dotnet add package AutoFixture.Xunit2

AutoData: Fully automatic parameter generation

AutoData is the most basic attribute, automatically generating test data for all parameters of a test method.

Basic Usage

using AutoFixture.Xunit2;

public class Person
{
    public Guid Id { get; set; }

    [StringLength(10)]
    public string Name { get; set; } = string.Empty;

    [Range(18, 80)]
    public int Age { get; set; }

    public string Email { get; set; } = string.Empty;
    public DateTime CreateTime { get; set; }
}

[Theory]
[AutoData]
public void AutoData_應能自動產生所有參數(Person person, string message, int count)
{
    // Arrange & Act - 參數已由 AutoData 自動產生

    // Assert
    person.Should().NotBeNull();
    person.Id.Should().NotBe(Guid.Empty);
    person.Name.Should().HaveLength(10);     // 遵循 StringLength 限制
    person.Age.Should().BeInRange(18, 80);   // 遵循 Range 限制
    message.Should().NotBeNullOrEmpty();
    count.Should().NotBe(0);
}

Constraining parameters via DataAnnotation

[Theory]
[AutoData]
public void AutoData_透過DataAnnotation約束參數(
    [StringLength(5, MinimumLength = 3)] string shortName,
    [Range(1, 100)] int percentage,
    Person person)
{
    // Assert
    shortName.Length.Should().BeInRange(3, 5);
    percentage.Should().BeInRange(1, 100);
    person.Should().NotBeNull();
}

InlineAutoData: Mixing fixed values with automatic generation

InlineAutoData combines the fixed-value feature of InlineData with the automatic generation capability of AutoData .

Basic Syntax

[Theory]
[InlineAutoData("VIP客戶", 1000)]
[InlineAutoData("一般客戶", 500)]
[InlineAutoData("新客戶", 100)]
public void InlineAutoData_混合固定值與自動產生(
    string customerType,    // 對應第 1 個固定值
    decimal creditLimit,    // 對應第 2 個固定值
    Person person)          // 由 AutoFixture 產生
{
    // Arrange
    var customer = new Customer
    {
        Person = person,
        Type = customerType,
        CreditLimit = creditLimit
    };

    // Assert
    customer.Type.Should().Be(customerType);
    customer.CreditLimit.Should().BeOneOf(1000, 500, 100);
    customer.Person.Should().NotBeNull();
}

Parameter Order Consistency

The order of fixed values must match the order of method parameters:

[Theory]
[InlineAutoData("產品A", 100)]  // 依序對應 name, price
[InlineAutoData("產品B", 200)]
public void InlineAutoData_參數順序一致性(
    string name,        // 對應第 1 個固定值
    decimal price,      // 對應第 2 個固定值
    string description, // 由 AutoFixture 產生
    Product product)    // 由 AutoFixture 產生
{
    // Assert
    name.Should().BeOneOf("產品A", "產品B");
    price.Should().BeOneOf(100, 200);
    description.Should().NotBeNullOrEmpty();
    product.Should().NotBeNull();
}

Important limitation: only compile-time constants can be used

// 正確:使用常數
[InlineAutoData("VIP", 100000)]
[InlineAutoData("Premium", 50000)]

// 錯誤:不能使用變數
private const decimal VipCreditLimit = 100000m;
[InlineAutoData("VIP", VipCreditLimit)]  // 編譯錯誤

// 錯誤:不能使用運算式
[InlineAutoData("VIP", 100 * 1000)]  // 編譯錯誤

When complex data is needed, use MemberAutoData。

MemberAutoData: Combining external data sources

MemberAutoData allows test data to be obtained from a class's methods, properties, or fields.

Using a static method

public class MemberAutoDataTests
{
    public static IEnumerable<object[]> GetProductCategories()
    {
        yield return new object[] { "3C產品", "TECH" };
        yield return new object[] { "服飾配件", "FASHION" };
        yield return new object[] { "居家生活", "HOME" };
        yield return new object[] { "運動健身", "SPORTS" };
    }

    [Theory]
    [MemberAutoData(nameof(GetProductCategories))]
    public void MemberAutoData_使用靜態方法資料(
        string categoryName,   // 來自 GetProductCategories
        string categoryCode,   // 來自 GetProductCategories
        Product product)       // 由 AutoFixture 產生
    {
        // Arrange
        var categorizedProduct = new CategorizedProduct
        {
            Product = product,
            CategoryName = categoryName,
            CategoryCode = categoryCode
        };

        // Assert
        categorizedProduct.CategoryName.Should().Be(categoryName);
        categorizedProduct.CategoryCode.Should().Be(categoryCode);
        categorizedProduct.Product.Should().NotBeNull();
    }
}

Using a static property

public static IEnumerable<object[]> StatusTransitions => new[]
{
    new object[] { OrderStatus.Created, OrderStatus.Confirmed },
    new object[] { OrderStatus.Confirmed, OrderStatus.Shipped },
    new object[] { OrderStatus.Shipped, OrderStatus.Delivered },
    new object[] { OrderStatus.Delivered, OrderStatus.Completed }
};

[Theory]
[MemberAutoData(nameof(StatusTransitions))]
public void MemberAutoData_使用靜態屬性_訂單狀態轉換(
    OrderStatus fromStatus,
    OrderStatus toStatus,
    Order order)
{
    // Arrange
    order.Status = fromStatus;

    // Act
    order.TransitionTo(toStatus);

    // Assert
    order.Status.Should().Be(toStatus);
}

Custom AutoData Attributes

Create a dedicated AutoData configuration:

DomainAutoDataAttribute

public class DomainAutoDataAttribute : AutoDataAttribute
{
    public DomainAutoDataAttribute() : base(() => CreateFixture())
    {
    }

    private static IFixture CreateFixture()
    {
        var fixture = new Fixture();

        // 設定 Person 的自訂規則
        fixture.Customize<Person>(composer => composer
            .With(p => p.Age, () => Random.Shared.Next(18, 65))
            .With(p => p.Email, () => $"user{Random.Shared.Next(1000)}@example.com")
            .With(p => p.Name, () => $"測試用戶{Random.Shared.Next(100)}"));

        // 設定 Product 的自訂規則
        fixture.Customize<Product>(composer => composer
            .With(p => p.Price, () => Random.Shared.Next(100, 10000))
            .With(p => p.IsAvailable, true)
            .With(p => p.Name, () => $"產品{Random.Shared.Next(1000)}"));

        return fixture;
    }
}

BusinessAutoDataAttribute

public class BusinessAutoDataAttribute : AutoDataAttribute
{
    public BusinessAutoDataAttribute() : base(() => CreateFixture())
    {
    }

    private static IFixture CreateFixture()
    {
        var fixture = new Fixture();

        // 設定 Order 的業務規則
        fixture.Customize<Order>(composer => composer
            .With(o => o.Status, OrderStatus.Created)
            .With(o => o.Amount, () => Random.Shared.Next(1000, 50000))
            .With(o => o.OrderNumber, () => $"ORD{DateTime.Now:yyyyMMdd}{Random.Shared.Next(1000):D4}"));

        return fixture;
    }
}

Using a custom AutoData

[Theory]
[DomainAutoData]
public void 使用DomainAutoData(Person person, Product product)
{
    person.Age.Should().BeInRange(18, 64);
    person.Email.Should().EndWith("@example.com");
    product.IsAvailable.Should().BeTrue();
}

[Theory]
[BusinessAutoData]
public void 使用BusinessAutoData(Order order)
{
    order.Status.Should().Be(OrderStatus.Created);
    order.Amount.Should().BeInRange(1000, 49999);
    order.OrderNumber.Should().StartWith("ORD");
}

CompositeAutoData: Integrating multiple data sources

By inheriting AutoDataAttribute and merging the Customizations of multiple Fixtures through reflection, combine multiple custom AutoData configurations:

// 使用方式 - 自動套用 DomainAutoData + BusinessAutoData 的所有設定
[Theory]
[CompositeAutoData(typeof(DomainAutoDataAttribute), typeof(BusinessAutoDataAttribute))]
public void CompositeAutoData_整合多重資料來源(
    Person person, Product product, Order order)
{
    person.Age.Should().BeInRange(18, 64);       // DomainAutoData 的設定
    product.IsAvailable.Should().BeTrue();        // DomainAutoData 的設定
    order.Status.Should().Be(OrderStatus.Created); // BusinessAutoData 的設定
}

CollectionSizeAttribute: Controlling collection size

The default collection size for AutoData is 3. You can control the number of collection elements generated through a custom CollectionSizeAttribute . For the complete implementation and usage, refer to Complete Guide to CollectionSizeAttribute。

External Test Data Integration

Integrate external data sources such as CSV and JSON with MemberAutoData, while retaining AutoFixture's ability to automatically generate the remaining parameters. For the complete guide, refer to External Test Data Integration。

Data Source Design Patterns

Design patterns for hierarchical data organization and reusable data sets, creating a BaseTestData base class to centrally manage test data paths. For complete examples, refer to Data Source Design Patterns。

Working with Awesome Assertions

[Theory]
[InlineAutoData("VIP", 100000)]
[InlineAutoData("Premium", 50000)]
[InlineAutoData("Regular", 20000)]
public void AutoData與AwesomeAssertions協作_客戶等級驗證(
    string customerLevel,
    decimal expectedCreditLimit,
    [Range(1000, 15000)] decimal orderAmount,
    Customer customer,
    Order order)
{
    // Arrange
    customer.Type = customerLevel;
    customer.CreditLimit = expectedCreditLimit;
    order.Amount = orderAmount;

    // Act
    var canPlaceOrder = customer.CanPlaceOrder(order.Amount);
    var discountRate = CalculateDiscount(customer.Type, order.Amount);

    // Assert - 使用 Awesome Assertions 語法
    customer.Type.Should().Be(customerLevel);
    customer.CreditLimit.Should().Be(expectedCreditLimit);
    customer.CreditLimit.Should().BePositive();

    order.Amount.Should().BeInRange(1000m, 15000m);
    canPlaceOrder.Should().BeTrue();
    discountRate.Should().BeInRange(0m, 0.3m);
}

private static decimal CalculateDiscount(string customerType, decimal orderAmount)
{
    var baseDiscount = customerType switch
    {
        "VIP" => 0.15m,
        "Premium" => 0.10m,
        "Regular" => 0.05m,
        _ => 0m
    };

    var largeOrderBonus = orderAmount > 30000m ? 0.05m : 0m;
    return Math.Min(baseDiscount + largeOrderBonus, 0.3m);
}

Best Practices

Should Do

  1. Make good use of DataAnnotation

    • Use [StringLength]、[Range] on parameters to constrain the data range
    • Ensure the generated data conforms to business rules
  2. Create reusable custom AutoData

    • Create dedicated AutoData attributes for different domains
    • Centrally manage the generation rules for test data
  3. Use MemberAutoData to handle complex data

    • Use when InlineAutoData cannot meet the requirements
    • Supports variables, expressions, and external data sources
  4. Organize test data sources

    • Create a hierarchical data source structure
    • Manage related data centrally

Should be avoided

  1. Using non-constant values in InlineAutoData

    • InlineAutoData only accepts compile-time constants
    • Use MemberAutoData instead when dynamic values are needed
  2. Overly complex CompositeAutoData

    • Avoid combining too many AutoData sources
    • Keep the configuration understandable
  3. Ignoring parameter order

    • The fixed values of InlineAutoData must match the parameter order
    • Incorrect order will cause type mismatches

Code Templates

Please refer to the example files in the templates folder:

Relationship with Other Skills

  • autofixture-basics: Prerequisite knowledge for this skill
  • autofixture-customization: Customization strategies can be used for AutoData attributes
  • autofixture-nsubstitute-integration: Next learning goal
  • awesome-assertions-guide: Use together to improve test readability

Output Format

  • Generate tests using [Theory] with [AutoData] or [InlineAutoData] test methods
  • Generate custom AutoDataAttribute derived class files (*AutoDataAttribute.cs)
  • Test parameters are injected through attributes, reducing Arrange section code
  • Fixed-value parameters are placed before the method signature, and auto-generated parameters are placed after
  • Use DataAnnotation to constrain parameter ranges

Reference Resources

Original Article

The content of this skill is distilled from the "Old-School Software Engineer's Testing Training - 30-Day Challenge" article series:

Official Documentation