Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to efficiently create and use the builder pattern

On our last project we ended up with a shared test fixture for our unit tests which gave a lot of problems. So on our current project I've looked into the builder pattern. We run our unit tests in memory on the development machines and against the database on the build server.

Currently I have a T4 template which produces for example the following builder for a Student:

public class StudentBuilder : Builder<Student, StudentBuilder>
{
    public StudentBuilder()
    {
        IsMale = true;
    }

    public StudentBuilder WithFirstName(string firstName)
    {
        this.FirstName = firstName;
        return this;
    }

    public StudentBuilder WithLastName(string lastName)
    {
        this.LastName = lastName;
        return this;
    }

    public StudentBuilder WithIsMale(bool isMale)
    {
        this.IsMale = isMale;
        return this;
    }

    internal override Student Construct()
    {
        Student result = new Student()
        {
            FirstName = FirstName ?? "FirstName:" + id.ToString(),
            LastName = LastName ?? "LastName:" + id.ToString(),
            IsMale = IsMale,
            Id = id,
        };

     /   return result;
    }
}

Trough the base classes I can use this in the following way:

Student wouter = StudentBuilder.Build()
    .WithFirstName("Wouter")
    .WithLastName("de Kort");
List<Student> students = StudentBuilder.Build().Multiple(10, (builder, index) => builder.WithFirstName("FirstName" + index));

We run integration tests on our build server to make sure everything works against the database. This means we have to make sure that all referential constrains are met. But then the problems begin.

For example, a student is required to have a mentor, a mentor belongs to a school, a school to a city, a city to a ....

This would result in code like:

StudentBuilder.Build().WithMentor(MentorBuilder.Build().WithSchool(SchoolBuilder.Build().WithCity(CityBuilder.Build()))

How should I optimize this? I've thought about doing the 'default building' in the Construct method of each Builder but if I would build 10 students then it would lead to 10 mentors in 10 schools in 10 cities in 10....

Or maybe creating methods like WithAllCity(..), WithAll(School)

Any ideas? Am I actually using the Builder Pattern the right way? Could a Director class help? Or should I have inherited classes from StudentBuilder which solve these different cases?

Or another idea, should I add more validation in my service layer before sending the data to the database? Then I would catch more errors in my unit tests against the in memory database.

like image 397
Wouter de Kort Avatar asked Nov 05 '22 11:11

Wouter de Kort


1 Answers

If your unit test is going to be using the student's mentor, the mentor's school, and the school's city, I think it is reasonable for the unit test to have code to build all of that, but I suggest your unit test might not be testing just one thing. Make your unit tests more specific so that they are not drilling down through so many properties.

If the problem is not your unit tests, but that your student class demands a mentor to be fed into its constructor, and that mentor cannot be null, consider relaxing that requirement to allow a null mentor (my preference I suppose), or make the builder fill in a "default" object as you say. You could even make your default objects throw exceptions if you try to access their properties, prompting you that your unit test needs you to build an "actual" object.

like image 74
Joe Daley Avatar answered Nov 14 '22 08:11

Joe Daley