| name | dotnet-testing-autodata-xunit-integration |
| description | Complete guide for AutoFixture and xUnit integration. Use when you need to use AutoData or InlineAutoData to simplify xUnit parameterized test data preparation. Covers custom Customization and test data attributes to improve test readability and maintainability.
Keywords: AutoData, InlineAutoData, AutoFixture xUnit, [AutoData], [InlineAutoData], AutoDataAttribute, ICustomization, DataAttribute, parameterized test, Theory AutoData, MemberAutoData, test data attributes, fixture.Customize
|
| metadata | {"short-description":".NET skill guidance for dotnet-testing-autodata-xunit-integration"} |
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(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(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(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/)