Unit testing with InMemory and NUnit. (2024)

The most common approach to handle databases in unit tests, it seems, is to mock data access classes. There is absolutely nothing wrong with this approach, however if you are using C# and Entity Framework Core (EF Core), there is an alternative to mocking. It’s called InMemory, and in this post I will try to give a simple introduction on how to get started using InMemory with NUnit.

I should probably note that NUnit doesn’t have anything to do with EF Core’s InMemory option (or any other part of EF Core, for that matter), I am just using it, as it is the test suite that I am most comfortable with.

Unit testing with InMemory and NUnit. (2)

I will try to keep it as basic as possible, to keep to focus on the dynamics of EF Core InMemory and NUnit. The first thing we need is our DbContext. There is nothing in the context which reveals that it allows InMemory, but it is important that we have a constructor, which allows DbContextOptions as an input parameter.

public class StudentContext : DbContext
{
public StudentContext() { }
public StudentContext(DbContextOptions<StudentContext> options) : base(options) { }

public DbSet<Student> Students { get; set; }
}

Now since C# is a statically typed language, we cannot write the test, until we actually have the method we want to test (or at least we cannot compile or use Intellisense). So we will make simple method for adding a student to a database (if you want to follow TDD very strictly, you can wait with the actual implementation of the method).

First the student model…

public class Student
{
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}

Then the student processor…

public class StudentProcessor
{
private readonly StudentContext db;

public StudentProcessor(StudentContext db)
{
// The dependency injection is cruical. This allows us to
// easily switch between InMemory and actual databases.
this.db = db;
}

public void AddStudent(Student student)
{
// This is quite a simple validation. Real world
// scenarios usually involve a lot more complex code.
if (string.IsNullOrWhiteSpace(student.FirstName) || string.IsNullOrWhiteSpace(student.LastName))
{
var err = "Empty first/last name not allowed."
throw new ArgumentException(err);
}
db.Students.Add(student);
}
}

That’s it. All we want now is to test two simple scenarios:

  1. We can add a new student using our StudentProcessor class.
  2. The AddStudent() method fails, if first name or last name is blank.

Let’s get on with it…

Remember that I said it was important that we have a constructor on our StudentContext, which takes DbContextOptions as input? This is because our InMemory database is defined via the options. I usually make a helper method for creating InMemory contexts. Where you place it is up to you, but I often have it somewhere in my test project. It simply looks like this:

public StudentContext GetMemoryContext()
{
var options = new DbContextOptionsBuilder<StudentContext>()
.UseInMemoryDatabase(databaseName: "InMemoryDatabase")
.Options;
return new StudentContext(options);
}

Now all we need are the actual tests. They simply look like this:

public class UnitTests
{
[Test]
public void CanAddStudent()
{
var student = new Student
{ FirstName = "Jakob", LastName = "Soerensen" };
var db = GetMemoryContext();
var processor = new StudentProcessor(db);
processor.AddStudent(student);
}
[Test]
public void AddStudentFailsOnBlankFirstName()
{
// FirstName should not be allowed to be null.
var student = new Student
{ FirstName = null, LastName = "Soerensen" };
var db = GetMemoryContext();
var processor = new StudentProcessor(db);
// NUnit allows you to check if a specific type of
// exception is thrown. In this particular scenario,
// we would expect an ArgumentException.
Assert.Throws<ArgumentException>(() =>
processor.AddStudent(student));
}
}

As you can see it looks just as if you we’re using a “real” DbContext (i.e. one which points to an actual database). So you simply write tests as if you were using your normal database, only it will be much faster and you won’t have to maintain an actual database.

One thing to notice is that the even though the database is InMemory, it will persist over multiple tests. So you might want to add a [SetUp] method with NUnit, which deletes the database (and possibly seed it with test data). It could look something like this:

[SetUp]
public void InitDb()
{
var db = GetMemoryContext();
db.Database.EnsureDeleted();
db.Students.Add(new Student
{ FirstName = "John", LastName = "Doe" });
db.Students.Add(new Student
{ FirstName = "Jane", LastName = "Doe" });
db.SaveChanges();
}

Warning: Make sure that you only use .EnsureDeleted on your InMemory database. Accidentally wiping your production database won’t win you “Employee of the Month”. You can add the following line of code, to make sure that the database is InMemory:

if (db.Database.IsInMemory()) { // do stuff }

That’s all there is to it. Happy testing!

Unit testing with InMemory and NUnit. (2024)

References

Top Articles
Latest Posts
Article information

Author: Catherine Tremblay

Last Updated:

Views: 6156

Rating: 4.7 / 5 (47 voted)

Reviews: 86% of readers found this page helpful

Author information

Name: Catherine Tremblay

Birthday: 1999-09-23

Address: Suite 461 73643 Sherril Loaf, Dickinsonland, AZ 47941-2379

Phone: +2678139151039

Job: International Administration Supervisor

Hobby: Dowsing, Snowboarding, Rowing, Beekeeping, Calligraphy, Shooting, Air sports

Introduction: My name is Catherine Tremblay, I am a precious, perfect, tasty, enthusiastic, inexpensive, vast, kind person who loves writing and wants to share my knowledge and understanding with you.