TUnit
Updated
TUnit is a modern .NET test framework built on Microsoft.Testing.Platform. It uses data-source attributes to feed arguments into [Test] methods; you normally supply those values yourself (or with TUnit's built-in data sources).
AutoFixture.TUnit connects AutoFixture to TUnit. Attributes such as [AutoDataSource] and [AutoArguments] fill test parameters with anonymous specimens so you skip most of the arrange setup.
AutoFixture.TUnit currently targets AutoFixture 4 (4.18.1) and ships as a NuGet preview.
Prerequisites
- .NET 8+ test project (the package also supports
netstandard2.0consumers) - AutoFixture 4.18.1
- TUnit
Install
<PackageReference Include="AutoFixture" Version="4.18.1" />
<PackageReference Include="AutoFixture.TUnit" Version="0.1.0-preview0001" />
<PackageReference Include="TUnit" Version="1.66.8" />
AutoFixture.TUnit depends on TUnit.Core. Reference the full TUnit package in test projects for [Test] and assertions.
Example scenario
You are testing MyClass.Echo with TUnit. The test needs an integer and a MyClass instance without manual arrange code. Later tests freeze collaborators, mix inline rows with generated values, or share fixture setup across tests.
AutoDataSource
[Test, AutoDataSource]
public async Task AutoDataSource_ProvidesTestParameters(int expectedNumber, MyClass sut)
{
var result = sut.Echo(expectedNumber);
await Assert.That(result).IsEqualTo(expectedNumber);
}
AutoArguments
Mix explicit values with generated ones. Explicit arguments fill parameters left-to-right; AutoFixture generates the rest:
[Test]
[AutoArguments("alpha")]
[AutoArguments("alpha", "beta")]
public async Task AutoArguments_MixesExplicitAndGeneratedValues(string first, string second)
{
await Assert.That(first).IsEqualTo("alpha");
await Assert.That(string.IsNullOrEmpty(second)).IsFalse();
}
Prefer the generic form when a single inline value must stay strongly typed — especially arrays. Non-generic params object?[] can expand an array into multiple cells:
[Test, AutoArguments<int[]>([1, 2])]
public async Task AutoArguments_GenericArray_KeepsOneParameter(int[] values, MyClass sut)
{
await Assert.That(values).IsEquivalentTo([1, 2]);
await Assert.That(sut).IsNotNull();
}
AutoMemberDataSource and AutoClassDataSource
Use [AutoMemberDataSource] when some values come from a static member (property, field, or method). Use [AutoClassDataSource] when values come from a separate provider type. AutoFixture fills any remaining parameters.
public static IEnumerable<(int Left, int Right)> TupleRows =>
[
(2, 3),
(10, -4)
];
[Test, AutoMemberDataSource(nameof(TupleRows))]
public async Task AutoMemberDataSource_FillsRemainingParameters(
int a, int b, Calculator calculator)
{
await Assert.That(calculator.Add(a, b)).IsEqualTo(a + b);
}
[Test, AutoClassDataSource(typeof(KnownSumRows))]
public async Task AutoClassDataSource_FillsRemainingParameters(
int a, int b, Calculator calculator)
{
await Assert.That(calculator.Add(a, b)).IsEqualTo(a + b);
}
public class KnownSumRows : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
yield return [1, 1];
yield return [7, 8];
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Generic forms (C# 11+) avoid typeof(...) for the member host or provider type:
[Test, AutoMemberDataSource<CalculatorTests>(nameof(TupleRows))]
public async Task AutoMemberDataSource_GenericHost(int a, int b, Calculator calculator)
{
await Assert.That(calculator.Add(a, b)).IsEqualTo(a + b);
}
[Test, AutoClassDataSource<KnownSumRows>]
public async Task AutoClassDataSource_GenericProvider(int a, int b, Calculator calculator)
{
await Assert.That(calculator.Add(a, b)).IsEqualTo(a + b);
}
[AutoClassDataSource] is not TUnit's [ClassDataSource<T>]. TUnit's attribute injects an instance of T; AutoFixture's attribute supplies rows and generates leftover arguments. An empty member or class sequence yields no test rows.
Parameter attributes
Apply attributes on test parameters to customize how that parameter (and later parameters) are created.
Frozen
Without [Frozen], each parameter of the same type gets its own anonymous instance. [Frozen] tells AutoFixture: create this parameter once, then reuse that same instance for later requests of a matching type.
[Test, AutoDataSource]
public async Task Frozen_SharesSameInstance([Frozen] string first, string second)
{
await Assert.That(second).IsEqualTo(first);
}
Only parameters that come after the frozen one reuse it. Earlier parameters of the same type stay independent.
Frozen with Matching
By default, freeze matches the exact type only (Matching.ExactType). A frozen InMemoryOrderRepository would not satisfy an IOrderRepository constructor parameter.
Use Matching to widen what the frozen value can satisfy:
[Test, AutoDataSource]
public async Task Frozen_ByImplementedInterfaces_InjectsConcreteIntoInterface(
[Frozen(Matching.ImplementedInterfaces)] InMemoryOrderRepository repository,
OrderService sut,
Order order)
{
sut.PlaceOrder(order);
await Assert.That(repository.Saved).Contains(order);
}
OrderService needs IOrderRepository. With Matching.ImplementedInterfaces, the frozen concrete repository is also used wherever that interface is requested — so sut and repository share one instance and you can assert on repository.Saved.
| Flag | Matches requests for… |
|---|---|
ExactType | The same type as the parameter (default) |
DirectBaseType | The parameter's immediate base type |
ImplementedInterfaces | Interfaces the parameter type implements |
ParameterName | A constructor/method parameter with the same name |
PropertyName | A property with the same name |
FieldName | A field with the same name |
MemberName | Parameter, property, or field with the same name |
Combine flags with |, for example Matching.ExactType | Matching.ImplementedInterfaces.
Modest and Greedy
When a type has several constructors, AutoFixture must pick one. For MultiCtorProduct that means: parameterless, name-only, or name + price.
[Modest]— prefer the constructor with the fewest parameters (most modest). Here that is the parameterless constructor, so you get the hard-coded defaults"default"/0m.[Greedy]— prefer the constructor with the most parameters (greediest). Here that is(string name, decimal price), so AutoFixture generates anonymous values for both.
[Test, AutoDataSource]
public async Task ModestAttribute_UsesParameterlessConstructor([Modest] MultiCtorProduct product)
{
await Assert.That(product.Name).IsEqualTo("default");
await Assert.That(product.Price).IsEqualTo(0m);
}
[Test, AutoDataSource]
public async Task GreedyAttribute_UsesFullestConstructor([Greedy] MultiCtorProduct product)
{
await Assert.That(string.IsNullOrEmpty(product.Name)).IsFalse();
await Assert.That(product.Price).IsNotEqualTo(0m);
}
You can combine attributes on one parameter, for example [Frozen][Greedy]. See also Behaviors.
NoAutoProperties
By default, after construction AutoFixture assigns anonymous values to writable public properties. [NoAutoProperties] turns that off for the parameter's type — the instance is created, but properties keep their type defaults (here string.Empty for Name).
[Test, AutoDataSource]
public async Task NoAutoProperties_LeavesWritablePropertiesUnset([NoAutoProperties] Person person)
{
await Assert.That(person.Name).IsEqualTo(string.Empty);
}
FavorArrays, FavorLists, and FavorEnumerables
Like Modest/Greedy, these change which constructor is chosen when a type overloads constructors that take different collection shapes:
[FavorArrays]— prefer a constructor that takes an array (T[])[FavorLists]— prefer a constructor that takesIList<T>[FavorEnumerables]— prefer a constructor that takesIEnumerable<T>
Use them when the type exposes several collection constructors and the default choice is not the one your test needs.
Custom parameter attribute
Subclass CustomizeAttribute and return an ICustomization from GetCustomization. AutoFixture applies it when resolving that parameter:
using System.Reflection;
using AutoFixture;
using AutoFixture.TUnit;
public sealed class NamedAttribute(string name) : CustomizeAttribute
{
public override ICustomization GetCustomization(ParameterInfo parameter)
{
ArgumentNullException.ThrowIfNull(parameter);
return new NamedCustomization(name);
}
private sealed class NamedCustomization(string value) : ICustomization
{
public void Customize(IFixture fixture) => fixture.Inject(value);
}
}
[Test, AutoDataSource]
public async Task CustomParameterAttribute_InjectsConfiguredValue([Named("widget")] string name)
{
await Assert.That(name).IsEqualTo("widget");
}
[Named("widget")] injects the fixed string "widget" for that parameter instead of an anonymous value. Use custom attributes for team-specific rules that do not already exist as built-ins. Combine with [Frozen] when later parameters should reuse the same value.
Custom AutoDataSource attribute
When tests need the same fixture setup (for example AutoMoq), define a custom attribute instead of repeating Customize in every test.
Add AutoFixture.AutoMoq 4.18.1 when the factory uses AutoMoqCustomization. The AutoMoq guide documents the same customization on AutoFixture 5 — keep AutoFixture and AutoMoq on matching major versions.
public class AutoMoqDataSourceAttribute : AutoDataSourceAttribute
{
public AutoMoqDataSourceAttribute()
: base(() => new Fixture().Customize(new AutoMoqCustomization()))
{
}
}
[Test, AutoMoqDataSource]
public async Task CustomAutoDataSource_UsesFixtureFactory(OrderService service, Order order)
{
await Assert.That(service).IsNotNull();
await Assert.That(() => service.PlaceOrder(order)).ThrowsNothing();
}
Subclass AutoDataSourceAttribute and pass a factory Func<IFixture> to the base constructor. The factory runs once per test case and supplies the configured fixture; parameters are resolved from that fixture.
Custom AutoArguments attribute
Subclass AutoArgumentsAttribute the same way — pass a fixture factory plus the inline values:
public class AutoMoqArgumentsAttribute : AutoArgumentsAttribute
{
public AutoMoqArgumentsAttribute(params object?[] values)
: base(() => new Fixture().Customize(new AutoMoqCustomization()), values)
{
}
}
[Test]
[AutoMoqArguments(42)]
public async Task CustomAutoArguments_MixesExplicitValuesWithCustomFixture(
int orderId,
OrderService service,
string productName)
{
await Assert.That(orderId).IsEqualTo(42);
await Assert.That(service).IsNotNull();
await Assert.That(string.IsNullOrEmpty(productName)).IsFalse();
await Assert.That(() => service.PlaceOrder(new Order(orderId, productName))).ThrowsNothing();
}
Explicit values still fill parameters left-to-right; AutoFixture fills the rest from the customized fixture.
How it works
[AutoDataSource]— TUnit[Test]methods get anonymous parameters from AutoFixture[AutoArguments(...)]/[AutoArguments<T>(...)]— explicit inline values fill parameters left-to-right; AutoFixture generates the rest[AutoMemberDataSource]/[AutoClassDataSource]— member or class rows supply some columns; AutoFixture fills the rest[Frozen]/[Frozen(Matching...)]— create once; reuse for later matching requests (exact type, interfaces, names, …)[Modest]/[Greedy]— fewest vs most constructor parameters[NoAutoProperties]— construct without filling writable properties[FavorArrays]/[FavorLists]/[FavorEnumerables]— prefer constructors that take that collection shape- Custom
CustomizeAttribute— return anICustomizationfor that parameter - Subclass
AutoDataSourceAttributeorAutoArgumentsAttributeto share fixture setup across tests
Next steps
- Integrations overview
- xUnit.net 3
- NUnit 4
- AutoMoq
- Behaviors
- Register, Freeze, and Inject
- NuGet packages
- FAQ