Source: kevintsengtw/dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.
AutoData Attribute Family: xUnit and AutoFixture Integration Application
Applicable Scenarios
- AutoData
- InlineAutoData
- MemberAutoData
- CompositeAutoData
- xUnit AutoFixture integration
- parameterized test data
- test parameter injection
- CollectionSizeAttribute
- external test data
- CSV test data
- JSON test data
Overview
The AutoData attribute family is a feature provided by the AutoFixture.Xunit2 package, integrating AutoFixture's data
generation capabilities with xUnit's parameterized tests, allowing test parameters to be automatically injected,
significantly reducing test preparation code.
Core Features
- AutoData: Automatically generate all test parameters
- InlineAutoData: Mix fixed values with auto-generation
- MemberAutoData: Combine external data sources
- CompositeAutoData: Multiple data source integration
- CollectionSizeAttribute: Control collection generation quantity
Package Installation
<PackageReference Include="AutoFixture" Version="4.18.1" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.18.1" />
```text
```bash
dotnet add package AutoFixture.Xunit2
```text
## AutoData: Fully Automatic Parameter Generation
`AutoData` is the most basic attribute, automatically generating test data for all parameters of a test method.
### Basic Usage
```csharp
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_Should_Automatically_Generate_All_Parameters(Person person, string message, int count)
{
// Arrange & Act - Parameters automatically generated by AutoData
// Assert
person.Should().NotBeNull();
person.Id.Should().NotBe(Guid.Empty);
person.Name.Should().HaveLength(10); // Follows StringLength constraint
person.Age.Should().BeInRange(18, 80); // Follows Range constraint
message.Should().NotBeNullOrEmpty();
count.Should().NotBe(0);
}
```text
### Constrain Parameters via DataAnnotation
```csharp
[Theory]
[AutoData]
public void AutoData_Constrain_Parameters_Via_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();
}
```text
## InlineAutoData: Mixing Fixed Values with Auto-Generation
`InlineAutoData` combines the fixed value characteristic of `InlineData` with the auto-generation functionality of `AutoData`.
### Basic Syntax
```csharp
[Theory]
[InlineAutoData("VIP Customer", 1000)]
[InlineAutoData("Regular Customer", 500)]
[InlineAutoData("New Customer", 100)]
public void InlineAutoData_Mix_Fixed_Values_With_Auto_Generate(
string customerType, // Corresponds to 1st fixed value
decimal creditLimit, // Corresponds to 2nd fixed value
Person person) // Generated by 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();
}
```text
### Parameter Order Consistency
Fixed value order must match method parameter order:
```csharp
[Theory]
[InlineAutoData("Product A", 100)] // Sequentially correspond to name, price
[InlineAutoData("Product B", 200)]
public void InlineAutoData_Parameter_Order_Consistency(
string name, // Corresponds to 1st fixed value
decimal price, // Corresponds to 2nd fixed value
string description, // Generated by AutoFixture
Product product) // Generated by AutoFixture
{
// Assert
name.Should().BeOneOf("Product A", "Product B");
price.Should().BeOneOf(100, 200);
description.Should().NotBeNullOrEmpty();
product.Should().NotBeNull();
}
```text
### ⚠️ Important Limitation: Only Compile-Time Constants
```csharp
// ✅ Correct: Use constants
[InlineAutoData("VIP", 100000)]
[InlineAutoData("Premium", 50000)]
// ❌ Wrong: Cannot use variables
private const decimal VipCreditLimit = 100000m;
[InlineAutoData("VIP", VipCreditLimit)] // Compile error
// ❌ Wrong: Cannot use expressions
[InlineAutoData("VIP", 100 * 1000)] // Compile error
```text
When complex data is needed, use `MemberAutoData` instead.
## MemberAutoData: Combining External Data Sources
`MemberAutoData` allows obtaining test data from class methods, properties, or fields.
### Using Static Methods
```csharp
public class MemberAutoDataTests
{
public static IEnumerable<object[]> GetProductCategories()
{
yield return new object[] { "3C Products", "TECH" };
yield return new object[] { "Clothing & Accessories", "FASHION" };
yield return new object[] { "Home & Living", "HOME" };
yield return new object[] { "Sports & Fitness", "SPORTS" };
}
[Theory]
[MemberAutoData(nameof(GetProductCategories))]
public void MemberAutoData_Using_Static_Method_Data(
string categoryName, // From GetProductCategories
string categoryCode, // From GetProductCategories
Product product) // Generated by 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();
}
}
```text
### Using Static Properties
```csharp
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_Using_Static_Property_Order_Status_Transition(
OrderStatus fromStatus,
OrderStatus toStatus,
Order order)
{
// Arrange
order.Status = fromStatus;
// Act
order.TransitionTo(toStatus);
// Assert
order.Status.Should().Be(toStatus);
}
```text
## Custom AutoData Attributes
Create dedicated AutoData configurations:
### DomainAutoDataAttribute
```csharp
public class DomainAutoDataAttribute : AutoDataAttribute
{
public DomainAutoDataAttribute() : base(() => CreateFixture())
{
}
private static IFixture CreateFixture()
{
var fixture = new Fixture();
// Set custom rules for 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, () => $"Test User{Random.Shared.Next(100)}"));
// Set custom rules for Product
fixture.Customize<Product>(composer => composer
.With(p => p.Price, () => Random.Shared.Next(100, 10000))
.With(p => p.IsAvailable, true)
.With(p => p.Name, () => $"Product{Random.Shared.Next(1000)}"));
return fixture;
}
}
```text
### BusinessAutoDataAttribute
```csharp
public class BusinessAutoDataAttribute : AutoDataAttribute
{
public BusinessAutoDataAttribute() : base(() => CreateFixture())
{
}
private static IFixture CreateFixture()
{
var fixture = new Fixture();
// Set business rules for 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;
}
}
```text
### Using Custom AutoData
```csharp
[Theory]
[DomainAutoData]
public void Using_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 Using_BusinessAutoData(Order order)
{
order.Status.Should().Be(OrderStatus.Created);
order.Amount.Should().BeInRange(1000, 49999);
order.OrderNumber.Should().StartWith("ORD");
}
```text
## CompositeAutoData: Multiple Data Source Integration
Combine multiple custom AutoData configurations by inheriting from `AutoDataAttribute` and merging Customizations from multiple Fixtures via reflection:
```csharp
// Usage - Automatically applies DomainAutoData + BusinessAutoData settings
[Theory]
[CompositeAutoData(typeof(DomainAutoDataAttribute), typeof(BusinessAutoDataAttribute))]
public void CompositeAutoData_Integrate_Multiple_Data_Sources(
Person person, Product product, Order order)
{
person.Age.Should().BeInRange(18, 64); // DomainAutoData setting
product.IsAvailable.Should().BeTrue(); // DomainAutoData setting
order.Status.Should().Be(OrderStatus.Created); // BusinessAutoData setting
}
```text
## CollectionSizeAttribute: Control Collection Size
AutoData's default collection size is 3, which can be controlled through custom `CollectionSizeAttribute` to set the number of collection elements generated. For complete implementation and usage, see [CollectionSizeAttribute Complete Guide](references/collection-size-attribute.md).
## External Test Data Integration
Integrate external data sources like CSV, JSON with MemberAutoData while retaining AutoFixture's ability to automatically generate remaining parameters. Complete guide see [External Test Data Integration](references/external-data-integration.md).
## Data Source Design Patterns
Hierarchical data organization and reusable dataset design patterns, creating `BaseTestData` base class to uniformly manage test data paths. Complete examples see [Data Source Design Patterns](references/data-source-patterns.md).
## Collaboration with Awesome Assertions
```csharp
[Theory]
[InlineAutoData("VIP", 100000)]
[InlineAutoData("Premium", 50000)]
[InlineAutoData("Regular", 20000)]
public void AutoData_With_AwesomeAssertions_Collaboration_Customer_Level_Validation(
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 - Using Awesome Assertions syntax
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);
}
```text
## Best Practices
### Should Do
1. **Leverage DataAnnotation**
- Use `[StringLength]`, `[Range]` on parameters to constrain data ranges
- Ensure generated data meets business rules
2. **Create Reusable Custom AutoData**
- Create dedicated AutoData attributes for different domains
- Centrally manage test data generation rules
3. **Use MemberAutoData for Complex Data**
- Use when InlineAutoData cannot meet requirements
- Supports variables, expressions, and external data sources
4. **Organize Test Data Sources**
- Create hierarchical data source structures
- Manage related data centrally
### Should Avoid
1. **Don't Use Non-Constants in InlineAutoData**
- InlineAutoData only accepts compile-time constants
- Use MemberAutoData for dynamic values
2. **Don't Overcomplicate CompositeAutoData**
- Avoid combining too many AutoData sources
- Keep configurations understandable
3. **Don't Ignore Parameter Order**
- InlineAutoData fixed values must match parameter order
- Wrong order causes type mismatch
## Code Templates
See example files in the [templates](./templates) folder:
- [autodata-attributes.cs](./templates/autodata-attributes.cs) - AutoData attribute family usage examples
- [external-data-integration.cs](./templates/external-data-integration.cs) - CSV/JSON external data integration
- [advanced-patterns.cs](./templates/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
## Reference Resources
### Original Articles
This skill content is distilled from the "Old School Software Engineer's Testing Practice - 30 Day Challenge" article series:
- **Day 12 - Combining AutoData: xUnit and AutoFixture Integration Application**
- Article: https://ithelp.ithome.com.tw/articles/10375296
- Sample Code: https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day12
### Official Documentation
- [AutoFixture Cheat Sheet - AutoData Theories](https://github.com/AutoFixture/AutoFixture/wiki/Cheat-Sheet#autodata-theories)
- [AutoDataAttribute API Reference](https://autofixture.io/api/AutoFixture.Xunit.AutoDataAttribute.html)
- [InlineAutoDataAttribute API Reference](https://autofixture.io/api/AutoFixture.Xunit.InlineAutoDataAttribute.html)
- [CsvHelper Official Documentation](https://joshclose.github.io/CsvHelper/)