Introduction
The work I mainly do is interacting with databases. Before Entity Framework this would be using SQL and executing strings (I know the bad old days) and the code was laborious to write. Now things are much better with Microsoft's ORM framework.
I have worked on a few projects where there was more than the usual CRUD operations. Often there are methods to Get All data which match a specific criteria and getting these tested manually can be time consuming. Thing developing a "Contacts" application and the table with all the contacts is developed in the from end (say in MVC & razer). In order to test the data base is working as expected you need to create the database, the service/repository calling it and then the front end. Then to test you run the application and hope it all works. That build can take 20 seconds to build each time. The cycle is laborious.
For this reason I often create integration tests for the database layer.
Prerequisites
To help and demonstrate the builder its useful to have a database. This is available at my GitHub account at https://github.com/ianbowyer/blog/tree/master/Builders. In short here are some database models to create a Contacts Database.
Initially this will be a single table database but later we will build onto it. Create a class library for you database, I called it Bowyer.Blog.Builders.Database and add the following classes.
Remember to add the following using NuGet:
- Microsoft.EntityFrameworkCore
public abstract class ContactBase
{
public int Id { get; set; }
public DateTime Created { get; set; }
}
public class Contact: ContactBase
{
public string FirstName { get; set; }
public string MiddleNames { get; set; }
public string LastName { get; set; }
public string Company { get; set; }
public string EmailAddress { get; set; }
}
public class ContactsDbContext : DbContext
{
public ContactsDbContext(DbContextOptions<ContactsDbContext> options)
: base(options)
{
}
public DbSet<Contact> Contact { get; set; }
}
While we are here its worth creating some more class libraries called which we will be adding to later:
- Bowyer.Blog.Builders.Builders
- Bowyer.Blog.Builders.Services
and an NUnit Test project:
- Bowyer.Blog.Builders.Services.IntegrationTests
Inside the Services.IntegrationTests test project add the following abstract class (TestWithSqlServer.cs ) so that we can create a database locally. We will inherit this in our integration tests
public abstract class TestWithSqlServer :IDisposable
{
private bool _isDisposed;
protected ContactsDbContext DbContext { get; }
private const string InMemoryConnectionString = "Server=localhost;Database=TestContactsDatabase;Trusted_Connection=True;";
protected DbContextOptions<ContactsDbContext> _options;
protected TestWithSqlServer()
{
_options = new DbContextOptionsBuilder<ContactsDbContext>()
.UseSqlServer(InMemoryConnectionString)
.Options;
DbContext = new ContactsDbContext(_options);
DbContext.Database.EnsureCreated();
}
protected void ClearDatabase()
{
DbContext.Contact.RemoveRange(DbContext.Contact);
DbContext.Address.RemoveRange(DbContext.Address);
DbContext.PhoneNumber.RemoveRange(DbContext.PhoneNumber);
DbContext.SaveChanges();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_isDisposed) return;
if (disposing)
{
DbContext.Dispose();
}
_isDisposed = true;
}
}
Here is a quick service which has a GetAll
method with a filter which is used later. The Interface is only there by habit as I would normally use normally dependency Injection when implementing the service outside of tests.
public class ContactService : IContactService
{
private readonly ContactsDbContext _contactsDbContext;
public ContactService(ContactsDbContext contactsDbContext)
{
_contactsDbContext = contactsDbContext;
}
public IList<Contact> GetAll(string filter)
{
return _contactsDbContext.Contact
.Where(w => w.FirstName.Contains(filter)
|| w.MiddleNames.Contains(filter)
|| w.LastName.Contains(filter)
|| w.EmailAddress.Contains(filter))
.ToList();
}
}
public interface IContactService
{
IList<Contact> GetAll(string filter);
}
The Problem
The problem with writing database integration tests is that you first need to populate them with data and although this is a simple example (one table) when you have solutions with multiple tables it can become a chore.
So I am going to write a Test which has some data in the database (3 records) and it will get the record from the database using a filter. Essentially this is a Get All Contacts type with a filter.
[Test]
public void BeforeBuildersTests()
{
// Arrange
var contact1 = new Contact()
{
FirstName = "FirstName1",
MiddleNames = "MiddleName1",
LastName = "LastName1",
EmailAddress = "Email@Emailaddress1.com",
};
var contact2 = new Contact()
{
FirstName = "FirstName2",
MiddleNames = "MiddleName2",
LastName = "LastName2",
EmailAddress = "Email@Emailaddress2.com",
};
var contact3 = new Contact()
{
FirstName = "FirstName3",
MiddleNames = "MiddleName3",
LastName = "LastName3",
EmailAddress = "Email@Emailaddress3.com",
};
var filter = "Name2";
DbContext.AddRange(contact1,contact2,contact3);
DbContext.SaveChanges();
// Act
var actual = _service.GetAll(filter);
// Assert
Assert.AreEqual("FirstName2", actual.First().FirstName);
}
For me that test is too long. There is too much setting up and the code is replicated a lot. When you get to create a GetAll method which introduces paging and you want to test it you will need to create maybe 20 or 100 contact records. At this point people stop reading tests.
By using a builder you can reduce the code to be something like:
[Test]
public void BeforeBuildersTests()
{
// Arrange
for (int i = 1; i >= 3; i++)
{
var builder= new ContactBuilder()
.WithFirstName($"FirstName{i}")
.WithMiddleNames($"MiddleName{i}")
.WithLastName($"LastName{i}")
.WithEmailAddress($"Email@Emailaddress{i}.com");
DbContext.Add(builder.Build());
}
var filter = "Name2";
DbContext.SaveChanges();
// Act
var actual = _service.GetAll(filter);
// Assert
Assert.AreEqual("FirstName2", actual.First().FirstName);
}
Ok so I could have written a loop with creating a Contact Object but more power comes later.
Writing the Builder class
Inside the Bowyer.Blog.Builders.Builders class library create an abstract class called BuilderBase. The builder is a generic class which you will pass in your entity class. It creates a BuilderEntity
property of your entity type and a method called Build which returns it. The BuilderBase class is a useful place to keep things that are shared across entities such as CreatedDate methods (more to come later)
public abstract class BuilderBase<T>
where T : class, new()
{
protected T BuilderEntity { get; }
protected BuilderBase()
{
BuilderEntity = new T();
}
public T Build()
{
return BuilderEntity;
}
}
Now we are going to create the Builder class. Its make up of three main areas
- The constructor - Where the object (such as Contact) is created
- Static properties - I create a property for each property that I specify in the constructor which are useful later
- "With" methods - these override the defaults
public class ContactBuilder : BuilderBase<Contact>
{
public ContactBuilder()
{
BuilderEntity.FirstName = DefaultFirstName;
BuilderEntity.MiddleNames = DefaultMiddleNames;
BuilderEntity.LastName = DefaultLastName;
BuilderEntity.Company = DefaultCompany;
BuilderEntity.EmailAddress = DefaultEmailAddress;
}
public static string DefaultFirstName { get; } = "Bob";
public static string DefaultMiddleNames { get; } = null;
public static string DefaultLastName { get; } = "Jones";
public static string DefaultCompany { get; } = null;
public static string DefaultEmailAddress { get; } = "bob.jones@email.com";
public ContactBuilder WithFirstName(string firstName)
{
BuilderEntity.FirstName = firstName;
return this;
}
public ContactBuilder WithMiddleNames(string middleNames)
{
BuilderEntity.MiddleNames = middleNames;
return this;
}
public ContactBuilder WithLastName(string lastName)
{
BuilderEntity.LastName = lastName;
return this;
}
public ContactBuilder WithCompany(string company)
{
BuilderEntity.Company = company;
return this;
}
public ContactBuilder WithEmailAddress(string emailAddress)
{
BuilderEntity.EmailAddress = emailAddress;
return this;
}
}
As you add more properties over time then you can add more "With" Methods and Default. The Builder class is really simple to keep up to date.
The important thing to remember is that the "With" methods returns itself allowing the nice fluent flow.
More Advanced Builders
In time you will want to add more tables off the contact table for example Addresses. The way to do this would be to create an AddressBuilder and then include a WithAddress method in the ContactBuilder passing in the AddressBuilder.
I'll demonstrate. Lets create an Address.cs
Entity...
public class Address : ContactBase
{
public Contact ContactId{ get; set; }
public string HouseNameOrNumber { get; set; }
public string Street { get; set; }
public string TownCity { get; set; }
public string County { get; set; }
public string PostCode { get; set; }
}
and then add Address into the ContactDbContext.cs
...
public DbSet<Address> Address { get; set; }
Then update Contact.cs
to include a list of addresses
public IList<Address> Addresses { get; set; }
Ok. So now we have a contact which can have multiple Addresses. Lets create the Address Builder...
public class AddressBuilder : BuilderBase<Address>
{
public AddressBuilder()
{
BuilderEntity.HouseNameOrNumber = DefaultHouseNameOrNumber;
BuilderEntity.Street = DefaultStreet;
BuilderEntity.TownCity = DefaultTownCity;
BuilderEntity.County = DefaultCounty;
BuilderEntity.PostCode = DefaultPostCode;
}
public static string DefaultHouseNameOrNumber { get; } = "42";
public static string DefaultStreet { get; } = "The Street";
public static string DefaultTownCity { get; } = "Small Town";
public static string DefaultCounty { get; } = "Big County";
public static string DefaultPostCode { get; } = "SM01 9BC";
public AddressBuilder WithHouseNameOrNumber(string houseNumber)
{
BuilderEntity.HouseNameOrNumber = houseNumber;
return this;
}
public AddressBuilder WithStreet(string street)
{
BuilderEntity.Street = street;
return this;
}
public AddressBuilder WithTownCity(string townCity)
{
BuilderEntity.TownCity = townCity;
return this;
}
public AddressBuilder WithPostCode(string postCode)
{
BuilderEntity.PostCode = postCode;
return this;
}
}
Now that the builder is created we can now use it in a test. This test adds addresses to a contact
[Test]
public void AfterBuilderTestsWithAddress()
{
// Arrange
DbContext.Add(new ContactBuilder()
.WithAddress(new AddressBuilder().WithHouseNameOrNumber("41"))
.WithAddress(new AddressBuilder().WithHouseNameOrNumber("42"))
.WithAddress(new AddressBuilder().WithHouseNameOrNumber("42A"))
.Build());
DbContext.SaveChanges();
var filter = ContactBuilder.DefaultFirstName;
// Act
var actual = _service.GetAll(filter);
// Assert
var streetNumbers = actual.SelectMany(s => s.Addresses).Select(s => s.HouseNameOrNumber).ToList();
Assert.Contains("41", streetNumbers);
Assert.Contains("42", streetNumbers);
Assert.Contains("42A", streetNumbers);
}
Wrapping up
I use these builder classes sometimes when developing services which rely on a database. Especially where there is complex data models to create before I can run some code against it. They are not limited to using it for Entities in Entity Framework but its generally where I am dealing with data.
This is my first Blog. Feel free to comment below:
Acknowledgement
I was inspired to use the Builder pattern from someone I worked with. The blog post was originally in Swift but I thought it would be good to leave a link for Gabby's post - Next-level Swift unit tests using the builder pattern. Thanks Gabby.