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:
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.
If you to learn about using React with ASP.NET Core you might find my book useful: