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
- AutoData: Automatically generates all test parameters
- InlineAutoData: Mixes fixed values with automatic generation
- MemberAutoData: Combines external data sources
- CompositeAutoData: Integrates multiple data sources
- 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
-
Make good use of DataAnnotation
- Use
[StringLength]、[Range]on parameters to constrain the data range - Ensure the generated data conforms to business rules
- Use
-
Create reusable custom AutoData
- Create dedicated AutoData attributes for different domains
- Centrally manage the generation rules for test data
-
Use MemberAutoData to handle complex data
- Use when InlineAutoData cannot meet the requirements
- Supports variables, expressions, and external data sources
-
Organize test data sources
- Create a hierarchical data source structure
- Manage related data centrally
Should be avoided
-
Using non-constant values in InlineAutoData
- InlineAutoData only accepts compile-time constants
- Use MemberAutoData instead when dynamic values are needed
-
Overly complex CompositeAutoData
- Avoid combining too many AutoData sources
- Keep the configuration understandable
-
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:
- autodata-attributes.cs - Example of using the AutoData attribute family
- external-data-integration.cs - CSV/JSON external data integration
- advanced-patterns.cs - Advanced patterns and CollectionSizeAttribute
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
AutoDataAttributederived 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:
- Day 12 - Combining AutoData: Integration of xUnit and AutoFixture
- Ironman series article:https://ithelp.ithome.com.tw/articles/10375296
- Sample code:https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day12