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.