# AutoFixture and AutoData: Reduce .NET Test Setup

A unit test can spend more lines constructing irrelevant objects and mocks than expressing the behavior it protects. That Arrange boilerplate slows reviews, obscures the important inputs, and becomes another structure to maintain when constructors change.

This article shows how AutoFixture, AutoData, and AutoNSubstitute can generate data and compose dependencies so a test keeps only the setup that matters. It is aimed at experienced .NET developers and technical leads evaluating whether convention-based fixture generation will improve—or hide—the intent of their test suite.

## Why Arrange Boilerplate Hurts Unit Tests

Who writes unit tests at work? Probably almost everyone reading this article.  
Who runs short of time while writing them? Probably almost everyone who writes them.

I will assume unit tests need no defense here.

- Building a new feature? Verify the implementation.
- Found a bug? Cover the fix with a test.
- Trying to understand legacy code? Debug it through the test runner.

No matter how many AI tools appear, the situation remains familiar: the sprint has been packed with business features, and your manager looks like this:

![Developer pulled from test setup toward production](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/z37xl3frcb6s7akpz32p.png)

The problem is a lack of time. Before we can tackle it, though, we need to talk about…

## Arrange, Act, and Assert in .NET Tests

Teams use different libraries and tools, but in my commercial experience, two conventions work well for organizing and structuring test code.

I am talking, of course, about AAA and Roy Osherove's naming convention.

### AAA in Plain English

As you may have guessed, this is not about batteries or video games. It is about unit testing.  
Programming is already complex enough; navigating a codebase becomes even harder when everything is scattered and nothing is where it belongs.

Life is much easier when everything has a clear place.

Tests can also be organized into clear, concrete stages:

- **Arrange.** Set up the test: create objects, prepare data, configure mocks, and so on.
- **Act.** Perform the action—call the functionality under test.
- **Assert.** Verify the result. Check the data, object state, expected calls, errors, or anything else the scenario requires.

Arrange-Act-Assert gives tests a simple, effective, and recognizable structure.

### Roy Osherove in Plain English

A second convention covers test names.

Roy Osherove's test-naming convention looks like this:

```text
UnitOfWork_StateUnderTest_ExpectedBehavior
```

For English speakers, the structure is mostly self-explanatory. A test name specifies:

- the unit of work,
- the state configured by the test,
- the expected behavior.

Does that look familiar? If it reminded you of AAA, you have the idea.

This simple rule is easy to adopt, yet it improves project structure and navigation.

### Example

```csharp
public record Adder(int Value)
{
    public Adder Add(Adder that) =>
        new(Value + that.Value);
}

public class AdderTests
{
    [Fact]
    public void Add_AddingToZero_ResultNotAffectedByZero()
    {
        // arrange
        Adder ten = new(10);
        Adder zero = new(0);

        // act
        var sum = ten.Add(zero);

        // assert
        sum.Should().Be(ten);
    }
}
```

## The Cost of Manual Test-Data Setup

Why did we need to discuss these conventions? Because this kind of structure makes the process systematic—and makes bottlenecks obvious.

Unit tests really have one bottleneck: Arrange. Act and Assert usually take about two lines each. Arrange is where things get interesting—and **large**. Object setup, collections, integration behavior, configuration: it all lives there.

Now imagine that we are working on a typical task in an enterprise codebase. We write some thoroughly average code and end up with an equally average…

### A Typical Handler

```csharp
public class SomeHandler(
    ISomeRepository repository,
    ISomeProcessingService service,
    ISomeSessionAccessor sessionAccessor,
    ISomeExternalDataProvider provider,
    IOptions<SomeOptions> options,
    ILogger<SomeHandler> logger)
{
    private readonly SomeOptions _options = options.Value;

    public async Task<SomeResponse> Handle(SomeRequest request, CancellationToken ct)
    {
        if (request.Field1 < 0)
            throw new SomeException();
        var entity = await repository.GetByField2(request.Field2, ct);
        if (request.Field3)
        {
            await service.ProcessAsync(entity, ct);
            logger.LogInformation("processed");
        }
        var session = sessionAccessor.Session;
        session.ChangeStatus();
        var externalData = await Task.WhenAll(
            _options.Sources.Select(
                source => provider.GetCollectionBySource(source, ct)));
        return new SomeResponse(
            entity,
            externalData.SelectMany(x => x).ToArray());
    }
}
```

This is your average piece of JSON plumbing: it changes something in a session, logs something, reads some configuration, writes something to a database, and returns something to the outside world.

A meaningless and merciless sequence of symbols, even if it is semantically and syntactically valid.

Now let us write a unit test according to London-school practices. It will not even fit on a single PowerPoint slide.

I used NSubstitute for mocking: [www.nuget.org/packages/NSubstitute](https://www.nuget.org/packages/NSubstitute).  
I prefer it for its concise API and lack of [SponsorLink incidents](https://www.securitylab.ru/news/540800.php).

```csharp
public class SomeHandlerTests
{
    [Fact]
    public async Task Handle_HappyPath_DoesNotThrow()
    {
        var repository = Substitute.For<ISomeRepository>();
        repository.GetByField2(default, default)
            .ReturnsForAnyArgs(new SomeEntity(
                Field2: nameof(SomeEntity.Field2),
                Data: nameof(SomeEntity.Data)));
        var service = Substitute.For<ISomeProcessingService>();
        var sessionAccessor = Substitute.For<ISomeSessionAccessor>();
        sessionAccessor.Session.Returns(Substitute.For<ISession>());
        var provider = Substitute.For<ISomeExternalDataProvider>();
        provider.GetCollectionBySource(default, default)
            .ReturnsForAnyArgs([
                new SomeExternalData(Guid.NewGuid(), Content: 1.ToString(), [1, 2, 3]),
                new SomeExternalData(Guid.NewGuid(), Content: 2.ToString(), [4, 5, 6]),
            ]);
        var handler = new SomeHandler(
            repository,
            service,
            sessionAccessor,
            provider,
            Options.Create(new SomeOptions { Sources = ["source1", "source2"] }),
            NullLogger<SomeHandler>.Instance);
        var response = await handler.Handle(new SomeRequest(
                Field1: 123,
                Field2: "123",
                Field3: true),
            ct: default);
        Assert.NotNull(response);
    }
}
```

All that just to test the basic happy path.

And we still have branches, behavior changes, integration failures, and much more. You might extract SUT (system under test) creation into a separate method and call it from every test.

But the application contains other handlers and services. The code will be duplicated, and the boilerplate will grow at least linearly.

### What Can We Do?

Arrange has one simple goal: prepare the test.

To achieve it, boilerplate code prepares data and behavior for the scenario we want to run.

The answer practically suggests itself:

- Automate data preparation. Eliminate as many `new()` and `[]` calls as possible.
- Automate behavior preparation. Eliminate as much mock creation and setup as possible.

## Reducing Arrange with AutoFixture and AutoData

Before discussing how, let us meet the tools whose combination will solve the problem. These are several NuGet packages you may or may not have encountered. The first is…

### AutoFixture

[www.nuget.org/packages/AutoFixture](https://www.nuget.org/packages/AutoFixture)

This NuGet package can create instances of data types with public constructors:

```csharp
var fixture = new Fixture();
var bar = fixture.Create<Bar>();

record Foo(Guid Id);

record Bar(
    Foo Foo,
    string Name,
    bool IsBaz,
    int Number);
```

The library was created by Mark Seemann, author of _Dependency Injection in .NET_.

It solves exactly the problem we identified: when a meaningless 300-field DTO needs to go into the database, you can replace manual initialization with a one-line call.

The code above creates an object like this:

```json
{
    "Foo": {
        "Id": "537478f7-a6f0-4e85-8ca2-1d7e0da97e7e"
    },
    "Name": "Name0e4afc29-8ef7-4991-aaa1-2294a456cccc",
    "IsBaz": false,
    "Number": 58
}
```

### AutoData

[www.nuget.org/packages/AutoFixture.Xunit2/5.0.0-preview0011](https://www.nuget.org/packages/AutoFixture.Xunit2/5.0.0-preview0011)

I am lazy not only as a programmer: I do not want to instantiate `Fixture` objects and create every related object myself. I want everything ready by the time the test starts.

Fortunately, AutoFixture has connectors for test frameworks such as xUnit. Informally, I call this family of features AutoData, after the attribute that provides the behavior I need.

We apply the attribute to theory tests. It implicitly creates a data source whose `Fixture` instance produces the objects requested by the theory parameters.

```csharp
public class TestClass
{
    [Theory, AutoData]
    public void TestMethod1(string foo)
    {
        foo.Should().NotBeEmpty();
    }

    [Theory, AutoData]
    public void TestMethod2(Foo foo)
    {
        foo.Bar.Should().NotBeEmpty();
    }
}

public class Foo
{
    public string Bar { get; set; }
}
```

![debug view](https://habrastorage.org/r/w1560/webt/eu/9q/94/eu9q94dfre6t9fod6_r2lengsig.png)

Strictly speaking, this is a synthetic theory because it has only one case. But what if you already have data sources?

There are `InlineAutoData`, `MemberAutoData`, `ClassAutoData`, and other attributes:

```csharp
public class TestClass
{
    [Theory]
    [MemberAutoData(nameof(TestData))]
    public void TestMethod3(int a, int b, int c)
    {
        c.Should().BeGreaterThan(a + b);
    }

    public static IEnumerable<object[]> TestData =
    [
        [-1, -2],
        [-3, -4]
    ];
}
```

![green test run](https://habrastorage.org/r/w1560/webt/lr/zb/db/lrzbdb34jinp3gli2vuni3cqefi.png)

Here, the data source supplied the first two test parameters for each case, while AutoFixture created the third. One theory now covers every listed case without repeating incidental setup.

Data preparation is now automated, so we are ready to move on.

### AutoData + Mocks = AutoNSubstitute

What should we do about mocks?  
When preparing them, we create a mock instance and configure some return value—either an object or another mock.

What if we asked AutoFixture to create a mock whose return values were populated with the results of `Create` calls?

It turns out that the [AutoNSubstitute](https://www.nuget.org/packages/AutoFixture.AutoNSubstitute/) customization does exactly that.

NuGet packages also exist for Moq, RhinoMocks, and FakeItEasy.

To create mocks, enhance the data generator with `Customize`. The test looks like this:

```csharp
public class TestClassClass
{
    [Fact]
    public async Task Test()
    {
        var fixture = new Fixture()
            .Customize(
                new AutoNSubstituteCustomization
                {
                    ConfigureMembers = true
                });
        var provider = fixture.Create<ISomeExternalDataProvider>();
        var collection = await provider.GetCollectionBySource(
            source: fixture.Create<string>(),
            ct: default);
        Assert.NotEmpty(collection);
    }
}
```

![debugger value view](https://habrastorage.org/r/w1560/webt/20/oe/u0/20oeu0i6stepcundme2-vt5vuf4.png)

The debugger screenshot shows the service instance that AutoFixture created for `ISomeExternalDataProvider`, including the automatically mocked method's return value.

### Combining AutoFixture, AutoData, and AutoNSubstitute

We can now combine all three pieces behind a single attribute.

A test method marked with this attribute receives both objects and configured mocks:

```csharp
public class AutoNSubstituteDataAttribute() :
    AutoDataAttribute(
        () => new Fixture().Customize(
            new AutoNSubstituteCustomization
            {
                ConfigureMembers = true
            }));
```

This attribute is not included out of the box. Mark Seemann suggests creating it yourself in his blog, using inheritance as the extension model.

Return to our typical handler. The test that would not fit on any screen can now be rewritten in two lines, letting us ignore the routine setup and focus on scenarios that verify the logic:

```csharp
public class SomeHandlerTests2
{
    [Theory, AutoNSubstituteData]
    public async Task Handle_HappyPath_DoesNotThrow(
        SomeRequest request,
        SomeHandler handler)
    {
        var response = await handler.Handle(request, ct: default);
        Assert.NotNull(response);
    }

    [Theory, AutoNSubstituteData]
    public async Task Handle_Field1LessThanZero_ThrowsSomeException(
        SomeRequest request,
        SomeHandler handler)
    {
        await Assert.ThrowsAsync<SomeException>(
            () => handler.Handle(request with { Field1 = -1 }, ct: default));
    }

    [Theory, AutoNSubstituteData]
    public async Task Handle_NoExternalData_ItIsEmptyInResponse(
        SomeRequest request,
        [Frozen] ISomeExternalDataProvider provider,
        SomeHandler handler)
    {
        provider.GetCollectionBySource(default, default)
            .ReturnsForAnyArgs([]);
        var response = await handler.Handle(request, ct: default);
        response.ExternalDataCollection.Should().BeEmpty();
    }
}
```

### Advanced Scenarios

AutoFixture is a powerful data generator, but it cannot infer every domain rule or create exactly the data you want. You have to tell it how.

That brings us back to extending its behavior. Inheritance is not an ideal extension model here: one codebase may contain multiple domains and business-logic implementations, potentially causing an explosion of derived attributes that quickly become confusing.

We can modify the behavior another way. Here is a real example from practice. Suppose we have a Value Object that models a discrete rating from 1 to 5:

```csharp
public struct Rate
{
    public Rate(byte value)
    {
        if (value is >= 1 and <= 5)
            Value = value;
        else
            throw new ArgumentOutOfRangeException(
                nameof(value),
                value,
                message: "rate can be from 1 to 5");
    }

    public byte Value { get; }
}
```

If we ask for a rating object in a test, we get an error:

```csharp
public class TestClassClass
{
    [Theory, AutoData]
    public void Test22(Rate rate)
    {
        rate.Value.Should().NotBe(0);
    }
}
```

![exception](https://habrastorage.org/r/w1560/webt/f4/38/qf/f438qfw9kggps7_kbiu38aiap-u.png)

The reason is that AutoFixture generates data according to its own rules:

![autofixture int range](https://habrastorage.org/r/w1560/webt/lw/ol/we/lwolwehtfp8zgjb5ncwhzeg8baa.png)

### Generic Attributes + `static abstract`

Let us investigate. Look inside `[AutoData]` and you will see a constructor that accepts a `Func<Fixture>` delegate:

```csharp
public AutoDataAttribute()
    : this(() => new Fixture())
{
}
```

We can use that hook to configure the `Fixture` object however we need. But how do we obtain different services and dependencies from a static constructor context, especially when attribute parameters can contain only constants and types?

Two C# 11 features help us: `static abstract` members and generic attributes. They work surprisingly well together.

```csharp
public interface IFixtureCustomizer
{
    static abstract void Customize(IFixture fixture);
}

public class AutoDataAttribute<TFixtureCustomizer> : AutoDataAttribute
    where TFixtureCustomizer : IFixtureCustomizer
{
    public AutoDataAttribute() : base(
        fixtureFactory: () =>
        {
            var fixture = new Fixture();
            TFixtureCustomizer.Customize(fixture);
            return fixture;
        })
    {
    }
}
```

Our data-generator extension for ratings now looks like this:

```csharp
public class RateGenerator : IFixtureCustomizer
{
    public static void Customize(IFixture fixture) =>
        fixture.Register(() => new Rate((byte)Random.Shared.Next(1, 6)));
}

public class TestClassClass
{
    [Theory, AutoData<RateGenerator>]
    public void Test22(Rate rate)
    {
        rate.Value.Should().NotBe(0);
    }
}
```

If your project is still on .NET 6 or earlier, adapt the solution as follows:

- Make `IFixtureCustomizer` instance-based.
- Have the attribute extension accept a type supplied through `typeof`.
- Instantiate the customizer inside the attribute through `Activator.CreateInstance`.

### Test Data Types

You can also describe the desired behavior and test-data generation scenario with classes whose objects AutoData creates as theory parameters.

For example, AutoFixture uses the [Fare library to generate strings from a regular expression](https://www.nuget.org/packages/Fare).

I recently needed this in a pet project: a test had to generate random sequences of strings from a particular lexical grammar. Armed with the documentation and some ingenuity, I automated preparation of those strings with a class passed as a test parameter:

```csharp
public record LexerInput([property: MinLength(10), MaxLength(25)] TokenInput[] TokenInputs) : IReadOnlyList<string>
{
    public IEnumerator<string> GetEnumerator() =>
        TokenInputs.Select(x => x.Value).GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    public int Count => TokenInputs.Length;

    public string this[int index] => TokenInputs[index].Value;

    public override string ToString() =>
        TokenInputs.Aggregate(
            TokenInput.AdditiveIdentity,
            (x, y) => x + y).Value;
}

public record TokenInput([property: RegularExpression(TokenInput.Pattern)] string Value) :
    IAdditiveIdentity<TokenInput, TokenInput>,
    IAdditionOperators<TokenInput, TokenInput, TokenInput>
{
    [StringSyntax(StringSyntaxAttribute.Regex)]
    public const string Pattern = "[a-zA-Z]+|[0-9]+|[+]{2}";

    public static TokenInput operator +(TokenInput left, TokenInput right) =>
        new(left.Value + " " + right.Value);

    public static TokenInput AdditiveIdentity { get; } = new(string.Empty);
}
```

AutoFixture reads data-creation rules from attributes in the `System.ComponentModel.DataAnnotations` namespace.

Here, `[RegularExpression]` supplies the pattern used to initialize the annotated string property, while `[MinLength]` and `[MaxLength]` provide the bounds for choosing a random array length.  
The test uses it like this:

```csharp
public class RegexLexerTests(ITestOutputHelper output)
{
    [Theory, AutoHydraScriptData]
    public void GetTokens_MockedRegex_ValidOutput(
        LexerInput input,
        [Frozen] IStructure structure,
        RegexLexer lexer)
    {
        output.WriteLine(input.ToString());
        var patterns = TokenInput.Pattern.Split('|');
        structure.Regex.ReturnsForAnyArgs(
            new Regex(string.Join('|', patterns.Select((x, i) => $"(?<TYPE{i}>{x})"))));
        var tokenTypes = Enumerable.Range(0, patterns.Length)
            .Select(x => new TokenType($"TYPE{x}"))
            .ToList();
        // ReSharper disable once GenericEnumeratorNotDisposed
        structure.GetEnumerator()
            .ReturnsForAnyArgs(_ => tokenTypes.GetEnumerator());
        var tokens = lexer.GetTokens(input.ToString());
        for (var i = 0; i < input.Count; i++)
        {
            output.WriteLine(tokens[i].ToString());
            tokens[i].Value.Should().BeEquivalentTo(input[i]);
            tokens[i].Type.Should().BeOneOf(tokenTypes);
        }
    }
}
```

## Takeaways

AutoFixture can automate both parts of Arrange:

- Data preparation = AutoFixture + AutoData.
- Behavior preparation = AutoFixture + AutoData + mocks.

The technique works best when generated values are incidental to the behavior under test. Keep meaningful boundary values explicit, freeze dependencies whose identity matters, and add fixture customizations only when they encode a stable project-wide convention. If a reader must reverse-engineer the fixture to understand the scenario, the abstraction has removed too much Arrange.

Modern C# features let customizations remain type-safe even as the fixture setup becomes reusable across a large test suite.

![Developer feedback about AutoData](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/qh06r17vvf3cohpz0xph.png)

## Related .NET Content

- [Two Biggest MediatR Mistakes](https://steponeit.hashnode.dev/two-biggest-mediatr-mistakes)
- [How to Consume Rate-Limited APIs in .NET](https://steponeit.hashnode.dev/how-to-consume-rate-limited-apis-in-net)

Follow [StepOne on GitHub](https://github.com/Stepami) for open-source C#/.NET projects, compiler experiments, production-focused examples, and new releases.
