Testing EF Core Repositories with xUnit and an In Memory Db


I came across the EF Core In Memory database recently. It obviously won’t have all the features of a relational database but it might be useful when unit testing simple repository methods.

Let’s take it for a spin …

Starting point

We have the following repository that we want to test. We’re just going to test the Add method in this post:

public interface IPersonRepository
{
...
ICollection<Person> GetAll();
void Add(Person person);
}
public class PersonRepository: IPersonRepository
{
private PersonDataContext personDataContext;
public PersonRepository(PersonDataContext personDataContext)
{
this.personDataContext = personDataContext;
}
...
public ICollection<Person> GetAll()
{
return personDataContext.People.ToList();
}
public Person Add(Person person)
{
personDataContext.People.Add(person);
personDataContext.SaveChanges();
return person;
}
}

Here’s the EF data context and models behind our repository:

public class PersonDataContext: DbContext
{
public PersonDataContext() { }
public PersonDataContext(DbContextOptions<PersonDataContext> options): base(options) { }
public DbSet<Person> People { get; set; }
public DbSet<EmailAddress> EmailAddresses { get; set; }
}
public class Person
{
public int PersonId { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
public ICollection<EmailAddress> EmailAddresses { get; set; }
}
public class EmailAddress
{
public int EmailAddressId { get; set; }
public string Email { get; set; }
}

xUnit

So, let’s bring in xUnit by adding the following entries into

"dependencies": {
...
"xunit": "2.2.0-beta5-build3474",
"dotnet-test-xunit": "2.2.0-preview2-build1029"
},

We also need to tell Visual Studio that xUnit is going to be our test runner by setting the following as a root property in project.json as well:

"testRunner": "xunit"

Now we can start adding xUnit tests. Let’s just add a couple of simple tests to double check xUnit is wired up properly. Let’s add the following class containing a test that should pass and a test that should fail:

public class SimpleTest
{
[Fact]
public void PassingTest()
{
Assert.Equal(2, 2);
}
[Fact]
public void FailingTest()
{
Assert.Equal(2, 3);
}
}

If we open up Test Explorer you’ll see our tests and if you run them, 1 should pass and should 1 fail.

As a bonus, you can also run these tests from the command line by browsing to the app’s folder and running the following command:

Terminal window
dotnet test

Again, you should see 1 passing and 1 failing test.

In Memory Database

Before we test our repository, let’s bring in the In Memory database into our solution by adding the following dependency in

{
"dependencies": {
...
"Microsoft.EntityFrameworkCore.InMemory": "1.0.1"
},
...
}

Repository Tests

So, let’s start testing our Add method in our repository by creating a class and a method to test the storyline when a person has no email address:

public class PersonRepositoryTests
{
[Fact]
public void Add_WhenHaveNoEmail()
{
IPersonRepository sut = GetInMemoryPersonRepository();
Person person = new Person()
{
PersonId = 1,
FirstName = "fred",
Surname = "Blogs"
};
Person savedPerson = sut.Add(person);
Assert.Equal(1, sut.GetAll().Count());
Assert.Equal("fred", savedPerson.FirstName);
Assert.Equal("Blogs", savedPerson.Surname);
Assert.Null(savedPerson.EmailAddresses);
}
private IPersonRepository GetInMemoryPersonRepository()
{
DbContextOptions<PersonDataContext> options;
var builder = new DbContextOptionsBuilder<PersonDataContext>();
builder.UseInMemoryDatabase();
options = builder.Options;
PersonDataContext personDataContext = new PersonDataContext(options);
personDataContext.Database.EnsureDeleted();
personDataContext.Database.EnsureCreated();
return new PersonRepository(personDataContext);
}
}

GetInMemoryPersonRepository is a method that all our tests will use to spin up a PersonRepository containing no data. Line 26 tells our data context to use the In Memory database. Lines 29 and 30 ensures we have a new database with no data in it.

The test is straight forward. Lines 6-12 creates a repository and a person with no email address. Line 14 calls the Add method in our repository passing in the person. Lines 16-19 carry our checks.

If you run the tests, all should be good.

Now, let’s add a couple more tests to test adding a person with single and multiple email addresses:

[Fact]
public void Add_WhenHaveSingleEmail()
{
IPersonRepository sut = GetInMemoryPersonRepository();
Person person = new Person()
{
PersonId = 1,
FirstName = "fred",
Surname = "Blogs",
EmailAddresses = new List<EmailAddress>()
{
new EmailAddress()
{
EmailAddressId = 1,
Email = "fred.blogs@testmail.com"
}
}
};
Person savedPerson = sut.Add(person);
Assert.Equal(1, sut.GetAll().Count());
Assert.Equal("fred", savedPerson.FirstName);
Assert.Equal("Blogs", savedPerson.Surname);
Assert.Equal(1, savedPerson.EmailAddresses.Count());
Assert.Equal("fred.blogs@testmail.com", savedPerson.EmailAddresses.ToList()[0].Email);
}
[Fact]
public void Add_WhenHaveMultipleEmail()
{
IPersonRepository sut = GetInMemoryPersonRepository();
Person person = new Person()
{
PersonId = 1,
FirstName = "fred",
Surname = "Blogs",
EmailAddresses = new List<EmailAddress>()
{
new EmailAddress()
{
EmailAddressId = 1,
Email = "fred.blogs@testmail.com"
},
new EmailAddress()
{
EmailAddressId = 2,
Email = "fred.blogs@anothermail.com"
}
}
};
Person savedPerson = sut.Add(person);
Assert.Equal(1, sut.GetAll().Count());
Assert.Equal("fred", savedPerson.FirstName);
Assert.Equal("Blogs", savedPerson.Surname);
Assert.Equal(2, savedPerson.EmailAddresses.Count());
Assert.Equal("fred.blogs@testmail.com", savedPerson.EmailAddresses.ToList()[0].Email);
Assert.Equal("fred.blogs@anothermail.com", savedPerson.EmailAddresses.ToList()[1].Email);
}

The tests are straightforward, following the same structure as the 1st test, using GetInMemoryPersonRepository to spin up a PersonRepository with an In Memory database behind it.

If you run the tests, all should be good.

The tests are also quick - on my machine the three tests took 7, 13 and 624 ms.