A powerful test data builder generator for .NET 6 and later that uses C# source generators to automatically create fluent builder APIs for your test data.
Why Buildenator?
- 🚀 Zero boilerplate - Automatically generates builder classes from your entities
- 🎯 Type-safe - Compile-time validation of builder methods
- 🔄 Fast - Uses incremental source generators for minimal compilation overhead
- 🧪 Test-friendly - Built-in support for AutoFixture and mocking frameworks
- 📦 Flexible - Works with constructors, properties, collections, and more
- Installation
- Quick Start
- Features
- Configuration
- Advanced Usage
- Mocking Integration
- AutoFixture Integration
- Examples
- Troubleshooting
- Contributing
Install via NuGet Package Manager:
dotnet add package BuildenatorOr via Package Manager Console:
Install-Package BuildenatorOptional packages for additional features:
# For AutoFixture integration
dotnet add package Buildenator.Abstraction.AutoFixture
# For Moq mocking framework integration
dotnet add package Buildenator.Abstraction.Moqnamespace YourProject
{
public class User
{
public User(int id, string name)
{
Id = id;
Name = name;
}
public int Id { get; }
public string Name { get; }
public string? Email { get; set; }
}
}In your test project, create a partial builder class:
using Buildenator.Abstraction;
using YourProject;
namespace YourTestProject.Builders
{
[MakeBuilder(typeof(User))]
public partial class UserBuilder
{
}
}using YourTestProject.Builders;
using Xunit;
public class UserTests
{
[Fact]
public void User_ShouldHaveCorrectName()
{
// Arrange
var user = new UserBuilder()
.WithId(1)
.WithName("John Doe")
.WithEmail("[email protected]")
.Build();
// Assert
Assert.Equal("John Doe", user.Name);
Assert.Equal(1, user.Id);
}
}That's it! Buildenator automatically generates:
WithId(),WithName(),WithEmail()fluent methodsBuild()method to create the instanceBuildMany(count)method to create multiple instances- Optional static factory property and default build method
This project uses a modified semantic versioning scheme:
Format: N.X.Y.Z
- N - Minimum version of .NET required (e.g., 8 = .NET 8+)
- X.Y.Z - Standard semantic versioning (Major.Minor.Patch)
Automatically generates With<PropertyName> methods for all constructor parameters and settable properties, enabling a clean, readable fluent interface.
var user = UserBuilder.User
.WithId(42)
.WithName("Jane Doe")
.WithEmail("[email protected]")
.Build();Generate AddTo<PropertyName>(params T[] items) methods for collection properties, allowing incremental item addition.
Supported collection types:
- Interface types:
IEnumerable<T>,IReadOnlyList<T>,ICollection<T>,IList<T> - Concrete types:
List<T>,HashSet<T>, and any class implementingICollection<T>
Behavior:
Withmethods replace the entire collectionAddTomethods append items incrementally
var order = OrderBuilder.Order
.AddToItems("Product A", "Product B") // Append items
.AddToItems("Product C") // Append more
.WithPrices(new[] { 9.99m, 19.99m }) // Replace entire collection
.Build();Generate multiple test objects with a single call, each with unique values (when using AutoFixture).
var users = UserBuilder.User.BuildMany(10).ToList();
// Creates 10 unique User instancesBuilt-in integration with popular mocking frameworks:
- Moq - via
Buildenator.Abstraction.Moq - NSubstitute - automatic mocking of interface dependencies
- Configurable mocking strategies (All, None, WithoutGenericCollection)
Optional AutoFixture support for automatic test data generation with random, realistic values.
[MakeBuilder(typeof(User))]
[AutoFixtureConfiguration()]
public partial class UserBuilder { }
// All properties not explicitly set will have random values
var user = UserBuilder.User.Build();Override default behavior by implementing your own methods:
- Custom
Build()method - Custom
BuildMany()method - Custom
PostBuild()hook for post-processing - Custom
With*methods for specific properties
[MakeBuilder(typeof(User))]
public partial class UserBuilder
{
// Custom PostBuild hook
public void PostBuild(User user)
{
// Perform additional setup after building
user.CreatedAt = DateTime.UtcNow;
}
}Uses incremental source generators for fast compilation with minimal build-time impact. See performance benchmarks.
- Nullable reference type support - Respects C# nullable contexts
- Generic type support - Works with generic classes
- Private constructor handling - Use static factory methods
- Implicit casting - Optional implicit conversion to target type
- Static factory methods - Use custom factory methods instead of constructors
- Properties without public setters - Generate methods for inherited or private setters
- Multiple namespaces - Builders can be in different namespaces than entities
Buildenator offers flexible configuration at multiple levels:
- Assembly-level (global defaults) - Highest priority
- Class-level (per builder) - Overrides assembly defaults
- Built-in defaults - Framework fallbacks
Apply settings globally to all builders in your test project:
using Buildenator.Abstraction;
using Buildenator.Abstraction.AutoFixture;
using Buildenator.Abstraction.Moq;
// Global configuration for all builders
[assembly: BuildenatorConfiguration(
buildingMethodsPrefix: "With",
generateDefaultBuildMethod: true,
nullableStrategy: NullableStrategy.Default,
generateMethodsForUnreachableProperties: false,
implicitCast: false,
generateStaticPropertyForBuilderCreation: true
)]
// Optional: AutoFixture configuration
[assembly: AutoFixtureConfiguration(
fixtureTypeName: "YourNamespace.CustomFixture"
)]
// Optional: Moq configuration
[assembly: MoqConfiguration]Override global settings for specific builders:
[MakeBuilder(
typeof(User),
buildingMethodsPrefix: "Set", // Use "Set" instead of "With"
generateDefaultBuildMethod: true, // Generate BuildDefault() method
nullableStrategy: NullableStrategy.Enabled, // Force nullable context
generateMethodsForUnreachableProperties: true, // Generate methods for private setters
implicitCast: true, // Enable implicit casting
staticFactoryMethodName: nameof(User.CreateUser), // Use static factory method
generateStaticPropertyForBuilderCreation: true // Generate static User property
)]
public partial class UserBuilder { }Prefix for generated methods:
"With"→WithName(),WithEmail()"Set"→SetName(),SetEmail()
Generates a static BuildDefault() method with all parameters having default values:
var user = UserBuilder.BuildDefault(id: 1, name: "Default");Controls nullable reference type handling:
Default- Inherit from project settings (recommended)Enabled- Force nullable context enabledDisabled- Force nullable context disabled
When true, generates methods for properties without public setters (e.g., inherited properties with private setters).
When true, enables implicit conversion from builder to entity:
[MakeBuilder(typeof(User), implicitCast: true)]
public partial class UserBuilder { }
User user = UserBuilder.User.WithName("John"); // Implicit cast, no .Build() neededUse a static factory method instead of the constructor:
public class User
{
private User(int id, string name) { /* ... */ }
public static User CreateUser(int id, string name) => new User(id, name);
}
[MakeBuilder(typeof(User), staticFactoryMethodName: nameof(User.CreateUser))]
public partial class UserBuilder { }Generates a static property for fluent builder creation:
// When true:
var user = UserBuilder.User.WithName("John").Build();
// When false:
var user = new UserBuilder().WithName("John").Build();Buildenator provides powerful collection handling with both With and AddTo methods.
public class Order
{
public IEnumerable<string> Items { get; set; }
public List<decimal> Prices { get; set; }
public IReadOnlyList<int> Quantities { get; set; }
}
[MakeBuilder(typeof(Order), generateStaticPropertyForBuilderCreation: true)]
public partial class OrderBuilder { }Usage examples:
// Adding items incrementally
var order = OrderBuilder.Order
.AddToItems("Product A", "Product B")
.AddToItems("Product C") // Continues adding
.Build();
// order.Items = ["Product A", "Product B", "Product C"]
// Replacing entire collection, then adding
var order2 = OrderBuilder.Order
.WithItems(new[] { "Initial" })
.AddToItems("Additional")
.Build();
// order2.Items = ["Initial", "Additional"]
// Replacing after adding (With replaces everything)
var order3 = OrderBuilder.Order
.AddToItems("Item 1", "Item 2")
.WithItems(new[] { "Replaced" })
.Build();
// order3.Items = ["Replaced"]
// Multiple collections
var order4 = OrderBuilder.Order
.AddToItems("Widget")
.AddToPrices(9.99m, 19.99m)
.AddToQuantities(1, 2, 5)
.Build();Add custom logic after the default build process:
[MakeBuilder(typeof(User))]
public partial class UserBuilder
{
public void PostBuild(User user)
{
// Custom initialization
user.CreatedAt = DateTime.UtcNow;
user.IsActive = true;
// Validation
if (string.IsNullOrEmpty(user.Email))
{
user.Email = $"{user.Name.Replace(" ", "")}@example.com";
}
}
}Override the default BuildMany behavior:
[MakeBuilder(typeof(User), generateStaticPropertyForBuilderCreation: true)]
public partial class UserBuilder
{
public IEnumerable<User> BuildMany(int count = 3)
{
// Custom implementation - e.g., sequential IDs
for (int i = 1; i <= count; i++)
{
yield return WithId(i).Build();
}
}
}
var users = UserBuilder.User.BuildMany(5).ToList();
// Creates users with IDs 1, 2, 3, 4, 5Buildenator handles inherited properties correctly:
public class BaseEntity
{
public int Id { get; private set; }
protected string CreatedBy { get; protected set; }
}
public class User : BaseEntity
{
public string Name { get; set; }
}
[MakeBuilder(typeof(User), generateMethodsForUnreachableProperties: true)]
public partial class UserBuilder { }
var user = new UserBuilder()
.WithId(1) // Works with private setter
.WithCreatedBy("Admin") // Works with protected property
.WithName("John")
.Build();Buildenator supports generic classes:
public class Result<T>
{
public T Value { get; set; }
public bool IsSuccess { get; set; }
}
[MakeBuilder(typeof(Result<int>))]
public partial class IntResultBuilder { }
var result = new IntResultBuilder()
.WithValue(42)
.WithIsSuccess(true)
.Build();If your constructor is private or you have many of them and you want to use the one you want, Use custom factory methods instead of constructors:
public class User
{
private User(int id, string name)
{
Id = id;
Name = name;
}
public static User CreateUser(int id, string name)
{
var user = new User(id, name);
// Additional initialization
user.CreatedAt = DateTime.UtcNow;
return user;
}
public int Id { get; }
public string Name { get; }
public DateTime CreatedAt { get; private set; }
}
[MakeBuilder(typeof(User), staticFactoryMethodName: nameof(User.CreateUser))]
public partial class UserBuilder { }Define your own With methods for special logic:
[MakeBuilder(typeof(User))]
public partial class UserBuilder
{
// Buildenator won't generate WithEmail if you define it
public UserBuilder WithEmail(string email)
{
// Custom validation
if (!email.Contains("@"))
{
throw new ArgumentException("Invalid email format");
}
return WithEmail(email);
}
// Convenience method
public UserBuilder WithAdminRole()
{
return WithRole("Admin").WithIsActive(true);
}
}using Buildenator.Abstraction;
using Buildenator.Abstraction.AutoFixture;
using SampleProject;
namespace SampleTestProject.Builders
{
[MakeBuilder(typeof(DomainEntity))]
/* AutoFixture is optional. By adding it, the builder will use random data generator
for filling in not set up properties. */
[AutoFixtureConfiguration()]
public partial class DomainEntityBuilder
{
}
}Will generate something very close to this source code:
using System;
using System.Linq;
using Buildenator.Abstraction.Helpers;
using SampleProject;
using AutoFixture;
namespace SampleTestProject.Builders
{
public partial class DomainEntityBuilder
{
private readonly Fixture _fixture = new Fixture();
public DomainEntityBuilder()
{
}
private Nullbox<int>? _propertyIntGetter;
private Nullbox<string>? _propertyStringGetter;
public DomainEntityBuilder WithPropertyIntGetter(int value)
{
_propertyIntGetter = new Nullbox<int>(value);
return this;
}
public DomainEntityBuilder WithPropertyStringGetter(string value)
{
_propertyStringGetter = new Nullbox<string>(value);
return this;
}
public DomainEntity Build()
{
return new DomainEntity((_propertyIntGetter.HasValue ? _propertyIntGetter.Value : new Nullbox<int>(_fixture.Create<int>())).Object, (_propertyStringGetter.HasValue ? _propertyStringGetter.Value : new Nullbox<string>(_fixture.Create<string>())).Object)
{
};
}
public static DomainEntityBuilder DomainEntity => new DomainEntityBuilder();
public System.Collections.Generic.IEnumerable<DomainEntity> BuildMany(int count = 3)
{
return Enumerable.Range(0, count).Select(_ => Build());
}
public static DomainEntity BuildDefault(int _propertyIntGetter = default(int), string _propertyStringGetter = default(string))
{
return new DomainEntity(_propertyIntGetter, _propertyStringGetter)
{
};
}
}
}Buildenator integrates seamlessly with popular mocking frameworks to automatically mock interface dependencies.
dotnet add package Buildenator.Abstraction.MoqAssembly-level configuration:
using Buildenator.Abstraction.Moq;
[assembly: MoqConfiguration]Usage:
public interface IUserRepository
{
User GetById(int id);
}
public class UserService
{
public UserService(IUserRepository repository)
{
Repository = repository;
}
public IUserRepository Repository { get; }
}
[MakeBuilder(typeof(UserService))]
public partial class UserServiceBuilder { }
// IUserRepository is automatically mocked with Moq
var service = new UserServiceBuilder().Build();
Assert.NotNull(service.Repository); // Mocked instanceControl which interfaces get mocked:
public enum MockingInterfacesStrategy
{
None = 0, // No automatic mocking
All = 1, // Mock all interfaces
WithoutGenericCollection = 2 // Mock all except generic collections (IEnumerable<T>, etc.)
}Custom mocking configuration:
[assembly: MoqConfiguration(strategy: MockingInterfacesStrategy.WithoutGenericCollection)]You can always override automatic mocking:
var mockRepo = new Mock<IUserRepository>();
mockRepo.Setup(r => r.GetById(1)).Returns(new User { Id = 1, Name = "John" });
var service = new UserServiceBuilder()
.WithRepository(mockRepo.Object)
.Build();AutoFixture integration provides automatic random data generation for properties not explicitly set.
dotnet add package Buildenator.Abstraction.AutoFixtureAssembly-level configuration:
using Buildenator.Abstraction.AutoFixture;
[assembly: AutoFixtureConfiguration()]Or with a custom fixture:
using AutoFixture;
namespace YourTestProject
{
public class CustomFixture : Fixture
{
public CustomFixture()
{
// Custom AutoFixture configuration
this.Register(() => DateTime.UtcNow);
this.Customize(new AutoMoqCustomization());
}
}
}
[assembly: AutoFixtureConfiguration(
fixtureTypeName: "YourTestProject.CustomFixture"
)][MakeBuilder(typeof(User))]
[AutoFixtureConfiguration()]
public partial class UserBuilder { }// Properties not set explicitly get random values
var user = new UserBuilder()
.WithId(1) // Explicitly set
.Build(); // Name, Email, etc. get random values
// Generate multiple unique instances
var users = new UserBuilder().BuildMany(10).ToList();
// All 10 users have different random dataControl what gets auto-generated:
public enum FixtureInterfacesStrategy
{
None = 0, // Don't generate any interfaces
All = 1, // Generate all interfaces
OnlyGenericCollections = 2 // Only generate generic collections (default)
}using AutoFixture;
using AutoFixture.AutoMoq;
public class CustomFixture : Fixture
{
public CustomFixture()
{
this.Customize(new AutoMoqCustomization());
}
}
[assembly: AutoFixtureConfiguration(
fixtureTypeName: "YourTestProject.CustomFixture"
)]
[assembly: MoqConfiguration]public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
[MakeBuilder(typeof(Product), generateStaticPropertyForBuilderCreation: true)]
public partial class ProductBuilder { }
// Usage
var product = ProductBuilder.Product
.WithId(1)
.WithName("Widget")
.WithPrice(9.99m)
.Build();public class Order
{
public Order(int id, DateTime orderDate)
{
Id = id;
OrderDate = orderDate;
}
public int Id { get; }
public DateTime OrderDate { get; }
public string CustomerName { get; set; }
}
[MakeBuilder(typeof(Order))]
[AutoFixtureConfiguration()]
public partial class OrderBuilder { }
// Usage - constructor parameters become With methods
var order = new OrderBuilder()
.WithId(100)
.WithOrderDate(DateTime.Today)
.WithCustomerName("Acme Corp")
.Build();public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public List<Order> Orders { get; set; } = new();
public Address BillingAddress { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
}
[MakeBuilder(typeof(Customer), generateStaticPropertyForBuilderCreation: true)]
[AutoFixtureConfiguration()]
public partial class CustomerBuilder { }
[MakeBuilder(typeof(Address), generateStaticPropertyForBuilderCreation: true)]
public partial class AddressBuilder { }
// Usage
var customer = CustomerBuilder.Customer
.WithId(1)
.WithName("John Doe")
.AddToOrders(
new Order(1, DateTime.Today),
new Order(2, DateTime.Today.AddDays(-1))
)
.WithBillingAddress(
AddressBuilder.Address
.WithStreet("123 Main St")
.WithCity("Springfield")
.WithPostalCode("12345")
.Build()
)
.Build();public class UserServiceTests
{
private readonly UserBuilder _userBuilder;
public UserServiceTests()
{
_userBuilder = new UserBuilder();
}
[Fact]
public void Process_ActiveUser_ShouldSucceed()
{
// Arrange
var user = _userBuilder
.WithIsActive(true)
.WithRole("User")
.Build();
// Act & Assert
// ...
}
[Fact]
public void Process_InactiveUser_ShouldFail()
{
// Arrange
var user = _userBuilder
.WithIsActive(false)
.Build();
// Act & Assert
// ...
}
[Theory]
[InlineData("Admin")]
[InlineData("SuperUser")]
public void Process_PrivilegedUser_ShouldHaveAccess(string role)
{
// Arrange
var user = _userBuilder
.WithRole(role)
.Build();
// Act & Assert
// ...
}
}Problem: The builder doesn't compile or methods aren't available.
Solutions:
- Ensure you've marked the builder class as
partial - Check that
[MakeBuilder(typeof(YourEntity))]attribute is correctly applied - Rebuild the project completely (
dotnet clean && dotnet build) - Check that Buildenator package is properly installed
- For Visual Studio users: Close and reopen files or restart IDE
Problem: Changes to entity aren't reflected in builder.
Solutions:
- Clean and rebuild:
dotnet clean && dotnet build - Delete
binandobjfolders manually - Restart your IDE
Problem: Warnings about nullable reference types.
Solutions:
- Use
nullableStrategy: NullableStrategy.Defaultin configuration - Or configure explicitly per builder:
[MakeBuilder(typeof(User), nullableStrategy: NullableStrategy.Enabled)]
Problem: Entity has only private constructors.
Solution: Use static factory method:
[MakeBuilder(typeof(User), staticFactoryMethodName: nameof(User.CreateUser))]Problem: Properties with a private setter can't be set.
Solution: Enable unreachable properties:
[MakeBuilder(typeof(DerivedClass), generateMethodsForUnreachableProperties: true)]If compilation is slow:
- Ensure you're using the latest version of Buildenator
- Check that you're not generating builders for very large object graphs
- Consider splitting large builders into smaller, focused builders
- Review benchmarks for performance characteristics
- Check existing integration tests for examples
- Review the changelog for recent changes
- Open an issue on GitHub with:
- Buildenator version
- .NET version
- Minimal reproduction code
- Build output / error messages
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Clone the repository
- Build:
dotnet build - Run tests:
dotnet test - Run benchmarks:
cd Tests/Buildenator.Benchmarks && dotnet run -c Release
- Integration tests (priority #1): Add test entities to
Buildenator.IntegrationTests.Sourceand tests toBuildenator.IntegrationTests - Unit tests: Add to
Buildenator.UnitTestsfor utility code - See project documentation for detailed guidelines
This project is licensed under the MIT License - see the LICENSE file for details.
The following builder definition:
using Buildenator.Abstraction;
using Buildenator.Abstraction.AutoFixture;
using SampleProject;
namespace SampleTestProject.Builders
{
[MakeBuilder(typeof(DomainEntity))]
/* AutoFixture is optional. By adding it, the builder will use random data generator
for filling in not set up properties. */
[AutoFixtureConfiguration()]
public partial class DomainEntityBuilder
{
}
}Generates code similar to this:
using System;
using System.Linq;
using Buildenator.Abstraction.Helpers;
using SampleProject;
using AutoFixture;
namespace SampleTestProject.Builders
{
public partial class DomainEntityBuilder
{
private readonly Fixture _fixture = new Fixture();
public DomainEntityBuilder()
{
}
private Nullbox<int>? _propertyIntGetter;
private Nullbox<string>? _propertyStringGetter;
public DomainEntityBuilder WithPropertyIntGetter(int value)
{
_propertyIntGetter = new Nullbox<int>(value);
return this;
}
public DomainEntityBuilder WithPropertyStringGetter(string value)
{
_propertyStringGetter = new Nullbox<string>(value);
return this;
}
public DomainEntity Build()
{
return new DomainEntity(
(_propertyIntGetter.HasValue ? _propertyIntGetter.Value : new Nullbox<int>(_fixture.Create<int>())).Object,
(_propertyStringGetter.HasValue ? _propertyStringGetter.Value : new Nullbox<string>(_fixture.Create<string>())).Object)
{
};
}
public static DomainEntityBuilder DomainEntity => new DomainEntityBuilder();
public System.Collections.Generic.IEnumerable<DomainEntity> BuildMany(int count = 3)
{
return Enumerable.Range(0, count).Select(_ => Build());
}
public static DomainEntity BuildDefault(int _propertyIntGetter = default(int), string _propertyStringGetter = default(string))
{
return new DomainEntity(_propertyIntGetter, _propertyStringGetter)
{
};
}
}
}Key points:
- Properties not explicitly set with
With*methods get random values from AutoFixture - Each call to
Build()generates a new instance with fresh random data BuildMany(count)creates multiple unique instancesBuildDefault()creates instance with default values (no randomization)- Static property
DomainEntityprovides fluent API entry point
For more examples, check the integration tests.
Made with ❤️ for the .NET testing community