Forever Factory helps you to easily build lots of custom objects. Some situations where it shines and is most helpful are:
- Creating test objects for any kind of automated tests, like unit, functional or acceptance tests
- Creating objects to return from mocked services
- Creating test data for when you are developing or testing new applications
With ForeverFactory, building a new object can be as simple as MagicFactory.For<Person>().Build()
.
You may install ForeverFactory with NuGet:
Install-Package ForeverFactory
Or via the .NET Core command line interface:
dotnet add package ForeverFactory
Either commands, from Package Manager Console or .NET Core CLI, will download and install ForeverFactory.
Let's assume we have a class named Person:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
We can build a new instance of this class as follows:
var person = MagicFactory.For<Person>().Build();
And we can customize each property:
var vitorHugo = MagicFactory.For<Person>()
.With(x => x.FirstName = "Vitor")
.With(x => x.LastName = "Hugo")
.With(x => x.Email = $"{x.FirstName}.{LastName}@gmail.com".ToLower())
.Build();
We can build a defined number of instances of this class as follows:
var hundredPeople = MagicFactory.For<Person>().Many(100).Build();
We can even customize a subset of the instances with custom properties:
var hundredPeople = MagicFactory.For<Person>()
.Many(100)
.With(x => x.Age = 100) // applies to all instances
.WithFirst(10, x => x.FirstName = "Isaac") // applies only to the first 10 instances
.WithLast(20, x => x.LastName = "Clarke") // applies only to the last 20 instances
.Build();
Additionally, we can create multiple sets of objects:
var people = MagicFactory.For<Person>()
.Many(10).With(x => x.Age = 5)
.Plus(20).With(x => x.Age = 60)
.PlusOne().With(x => x.Age = 100)
.Build(); // creates 10 persons with age 5, 20 with age 60, and 1 with age 100
You can maximize reuse of common customization by creating your own factory, with predefined rules, like this:
public class PersonFactory : MagicFactory<Person>
{
protected override void Customize(ICustomizeFactoryOptions<Person> customization)
{
customization
.Set(x => x.FirstName = "Albert")
.Set(x => x.LastName = "Einstein")
.Set(x => x.Age = 56);
}
}
With this factory, you can generate many objects like this:
var person = new PersonFactory().Build();
person.FirstName.Should().Be("Albert");
person.LastName.Should().Be("Einstein");
person.Age.Should().Be(56);
This feature is heavily inspired by a Python project named factory_boy.
If your class has a constructor with parameters, like the one below, you'll have to configure it:
public class Product
{
public string Name { get; }
public string Category { get; }
public string Description { get; set; }
public Product(string name, string category)
{
Name = name;
Category = category;
}
}
If you are using a custom factory, you can set the constructor function there:
public class ProductFactory : MagicFactory<Product>
{
protected override void Customize(ICustomizeFactoryOptions<Product> customization)
{
customization
.UseConstructor(() => new Product("Nimbus 2000", "Brooms"))
.Set(x => x.Description = "Top of the line flying broom");
}
}
Otherwise, you can just set it when building an object:
var product = MagicFactory.For<Product>()
.UsingConstructor(() => new Product("Nimbus 2000", "Brooms"))
.With(x => x.Description = "Top of the line flying broom")
.Build();
You can change how ForeverFactory behaves when creating objects for you.
var product = MagicFactory.For<Product>()
.WithBehavior(new FillWithEmptyValuesBehavior())
.Build();
// or
private class ProductFactory : MagicFactory<Product>
{
protected override void Customize(ICustomizeFactoryOptions<Product> customization)
{
customization
.SetDefaultBehavior(new FillWithEmptyValuesBehavior());
}
}
By default, it will not fill anything, and it is up to you to fill any properties you need.
With this behavior, ForeverFactory will recursively initialize every property it can with sequential values. This is similar to the default behavior of NBuilder:
var customers = MagicFactory.For<Customer>()
.WithBehavior(new FillWithSequentialValuesBehavior())
.Many(2)
.Build();
customers[0].Name.Should().Be("Name1");
customers[0].Age.Should().Be(1);
customers[0].Address.ZipCode.Should().Be("ZipCode1");
customers[1].Name.Should().Be("Name2");
customers[1].Age.Should().Be(2);
customers[1].Address.ZipCode.Should().Be("ZipCode2");
public class Customer
{
public string Name { get; set; }
public int Age { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string ZipCode { get; set; }
}
With this behavior, ForeverFactory will recursively initialize every property it can with empty values. For example, the following class structure will resolve as shown below?
var customers = MagicFactory.For<Customer>()
.WithBehavior(new FillWithEmptyValuesBehavior())
.Many(2)
.Build();
customers[0].Name.Should().Be("");
customers[0].Age.Should().Be(0);
customers[0].Address.ZipCode.Should().Be("");
customers[1].Name.Should().Be("");
customers[1].Age.Should().Be(0);
customers[1].Address.ZipCode.Should().Be("0");
public class Customer
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string ZipCode { get; set; }
}
You can set a default behavior for an entire project using ForeverFactoryGlobalSettings
:
// this needs to be executed before any tests
ForeverFactoryGlobalSettings
.UseBehavior(new FillWithSequentialValuesBehavior(options =>
{
options.DateTimeOptions = new DateTimeSequenceOptions
{
DateTimeIncrements = DateTimeIncrements.Hours,
StartDate = 2.September(2020)
};
options.FillNullables = false;
options.Recursive = false;
}));
var instances = MagicFactory.For<ClassA>().Many(2).Build().ToArray();
var secondInstance = instances[1];
secondInstance.DateTimeProperty.Should().Be(2.September(2020).At(1.Hours()));
secondInstance.NullableDateTimeProperty.Should().BeNull("FillNullables option is set to false");
secondInstance.B.Should().BeNull("Recursive option is set to false");
You can always override the behavior for a specific scenario:
var instance = MagicFactory.For<ClassA>()
.WithBehavior(new DoNotFillBehavior())
.Build();
instance.Name.Should().BeNull();
var project = MagicFactory.For<Project>()
.With(x => x.ProjectName = "Apollo")
.Do(x => Console.WriteLine(x.ProjectName) // prints "Apollo"
.Build();
You can help this project in many ways. Here are some ideas:
- Improving tests
- Reporting issues
- Making pull requests
To assess the test suite quality, we use mutation testing with the tool Stryker.NET. After cloning this repository, you can install it by running the following command:
dotnet tool restore
Then, you can navigate to the test project directory and then run stryker:
cd ./tests/ForeverFactory.Tests
dotnet stryker
- Add Faker-like functionality, seamlessly integrated into the current APIs
- Elaborate examples combining ForeverFactory with Faker-like libraries, like:
- Rewrite README; make it more exciting
- [Breaking Change] Change Build() for returning an IList instead of an IEnumerable. This will avoid the inconvenience of having to cast to list or array in tests.
- Support custom constructor scoped by generator node (for now, custom constructors are shared along the linked builders)