47

I am trying use InMemory EF7 database for my xunit repository test.

But my problem is that when i try to Dispose the created context the in memory db persist. It means that one test involve other.

I have read this article Unit Testing Entity Framework 7 with the In Memory Data Store and I have tried to setup the context in the constructor of my TestClass. But this approach doesn't work. When I run tests separately everything is OK, but my first test method add something into DB and second test method start with dirty DB from previous test method. I try add IDispose into test class but method DatabaseContext and DB persist in memory. What I am doing wrong am i missing something?

My code looks like:

using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Xunit; namespace Fabric.Tests.Repositories { /// <summary> /// Test for TaskRepository /// </summary> public class TaskRepositoryTests:IDisposable { private readonly DatabaseContext contextMemory; /// <summary> /// Constructor /// </summary> public TaskRepositoryTests() { var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>(); optionsBuilder.UseInMemoryDatabase(); contextMemory = new DatabaseContext(optionsBuilder.Options); } /// <summary> /// Dispose DB /// </summary> public void Dispose() { //this has no effect if (contextMemory != null) { contextMemory.Dispose(); } } /// <summary> /// Positive Test for ListByAssigneeId method /// </summary> /// <returns></returns> [Fact] public async Task TasksRepositoryListByAssigneeId() { // Arrange var assigneeId = Guid.NewGuid(); var taskList = new List<TaskItem>(); //AssigneeId != assigneeId taskList.Add(new TaskItem() { AssigneeId = Guid.NewGuid(), CreatorId = Guid.NewGuid(), Description = "Descr 2", Done = false, Id = Guid.NewGuid(), Location = "Some location 2", Title = "Some title 2" }); taskList.Add(new TaskItem() { AssigneeId = assigneeId, CreatorId = Guid.NewGuid(), Description = "Descr", Done = false, Id = Guid.NewGuid(), Location = "Some location", Title = "Some title" }); taskList.Add(new TaskItem() { AssigneeId = assigneeId, CreatorId = Guid.NewGuid(), Description = "Descr 2", Done = false, Id = Guid.NewGuid(), Location = "Some location 2", Title = "Some title 2" }); //AssigneeId != assigneeId taskList.Add(new TaskItem() { AssigneeId = Guid.NewGuid(), CreatorId = Guid.NewGuid(), Description = "Descr 2", Done = false, Id = Guid.NewGuid(), Location = "Some location 2", Title = "Some title 2" }); //set up inmemory DB contextMemory.TaskItems.AddRange(taskList); //save context contextMemory.SaveChanges(); // Act var repository = new TaskRepository(contextMemory); var result = await repository.ListByAssigneeIdAsync(assigneeId); // Assert Assert.NotNull(result.Count()); foreach (var td in result) { Assert.Equal(assigneeId, td.AssigneeId); } } /// <summary> /// test for Add method /// (Skip = "not able to clear DB context yet") /// </summary> /// <returns></returns> [Fact] public async Task TasksRepositoryAdd() { var item = new TaskData() { AssigneeId = Guid.NewGuid(), CreatorId = Guid.NewGuid(), Description = "Descr", Done = false, Location = "Location", Title = "Title" }; // Act var repository = new TaskRepository(contextMemory); var result = await repository.Add(item); // Assert Assert.Equal(1, contextMemory.TaskItems.Count()); Assert.NotNull(result.Id); var dbRes = contextMemory.TaskItems.Where(s => s.Id == result.Id).SingleOrDefault(); Assert.NotNull(dbRes); Assert.Equal(result.Id, dbRes.Id); } } } 

I am using:

"Microsoft.EntityFrameworkCore.InMemory": "1.0.0" "Microsoft.EntityFrameworkCore": "1.0.0" "xunit": "2.2.0-beta2-build3300" 
3
  • 2
    You can name you dbs for every test. See my answer here stackoverflow.com/questions/34925833/… Commented Aug 11, 2016 at 12:20
  • Hi thanks for your answer this could work ... I will try it! Commented Aug 11, 2016 at 15:58
  • Tip: to generate and populate the TaskList of TaskItems, check out GenFu, eg: var taskList = GenFu.GenFu.ListOf<TaskItems>(20); Commented Oct 24, 2017 at 0:09

2 Answers 2

58

From the documentation,

Typically, EF creates a single IServiceProvider for all contexts of a given type in an AppDomain - meaning all context instances share the same InMemory database instance. By allowing one to be passed in, you can control the scope of the InMemory database.

Instead of making the test class disposable and trying to dispose the data context that way, create a new one for each test:

private static DbContextOptions<BloggingContext> CreateNewContextOptions() { // Create a fresh service provider, and therefore a fresh // InMemory database instance. var serviceProvider = new ServiceCollection() .AddEntityFrameworkInMemoryDatabase() .BuildServiceProvider(); // Create a new options instance telling the context to use an // InMemory database and the new service provider. var builder = new DbContextOptionsBuilder<DatabaseContext>(); builder.UseInMemoryDatabase() .UseInternalServiceProvider(serviceProvider); return builder.Options; } 

Then, in each test, new up a data context using this method:

using (var context = new DatabaseContext(CreateNewContextOptions())) { // Do all of your data access and assertions in here } 

This approach should get you a squeaky-clean in-memory database for each test.

Sign up to request clarification or add additional context in comments.

10 Comments

Hallo Nate! Thank you very much! It is working! You save my a**!
Hi Nate in last version constraints have been removed. But anyway I am using validation System.ComponentModel.DataAnnotations in my repositories before SvaeChanges and it works for me. :-)
@NateBarbettini Amazing, this works perfectly! But I'm still confused why this is really required. How is it not enough to specify a new database name each time? I'm using .UseInMemoryDatabase(Guid.NewGuid().ToString()), but still things get shared somehow.
@MEMark it's been awhile but have you seen this? github.com/aspnet/EntityFrameworkCore/issues/6872
This no longer works in EFC 5 with a creepy error: A call was made to 'ReplaceService', but Entity Framework is not building its own internal service provider. Either allow Entity Framework to build the service provider by removing the call to 'UseInternalServiceProvider', or build replacement services into the service provider before passing it to 'UseInternalServiceProvider'. even after providing a database name.
|
17

I think the answer Nate gave may be out of date now or maybe I am doing something wrong. UseInMemoryDatabase() now requires a db name.

Below is what I ended up with. I added a line to create a unique db name. I removed the using statements in favor of using the constructor and dispose that are called once for each test case.

There are some debug lines in there from my testing.

public class DeviceRepositoryTests : IClassFixture<DatabaseFixture>, IDisposable { private readonly DeviceDbContext _dbContext; private readonly DeviceRepository _repository; private readonly ITestOutputHelper _output; DatabaseFixture _dbFixture; public DeviceRepositoryTests(DatabaseFixture dbFixture, ITestOutputHelper output) { this._dbFixture = dbFixture; this._output = output; var dbOptBuilder = GetDbOptionsBuilder(); this._dbContext = new DeviceDbContext(dbOptBuilder.Options); this._repository = new DeviceRepository(_dbContext); DeviceDbContextSeed.EnsureSeedDataForContext(_dbContext); //_output.WriteLine($"Database: {_dbContext.Database.GetDbConnection().Database}\n" + _output.WriteLine($"" + $"Locations: {_dbContext.Locations.Count()} \n" + $"Devices: {_dbContext.Devices.Count()} \n" + $"Device Types: {_dbContext.DeviceTypes.Count()} \n\n"); //_output.WriteLine(deviceDbContextToString(_dbContext)); } public void Dispose() { _output.WriteLine($"" + $"Locations: {_dbContext.Locations.Count()} \n" + $"Devices: {_dbContext.Devices.Count()} \n" + $"Device Types: {_dbContext.DeviceTypes.Count()} \n\n"); _dbContext.Dispose(); } private static DbContextOptionsBuilder<DeviceDbContext> GetDbOptionsBuilder() { // The key to keeping the databases unique and not shared is // generating a unique db name for each. string dbName = Guid.NewGuid().ToString(); // Create a fresh service provider, and therefore a fresh // InMemory database instance. var serviceProvider = new ServiceCollection() .AddEntityFrameworkInMemoryDatabase() .BuildServiceProvider(); // Create a new options instance telling the context to use an // InMemory database and the new service provider. var builder = new DbContextOptionsBuilder<DeviceDbContext>(); builder.UseInMemoryDatabase(dbName) .UseInternalServiceProvider(serviceProvider); return builder; } 

Here is a very basic test case.

[Fact] public void LocationExists_True() { Assert.True(_repository.LocationExists(_dbFixture.GoodLocationId)); } 

I also made 8 of test cases that attempted to delete the same device with the same id and each passed.

3 Comments

It working perfectly. If anyone will have some information what is lifetime of the database in memory it will be great to share!
Does the unique Db gets saved in a temporary file ? I tried to close the process and reopen, it still persists the data. If we create a new file everytime would it be a good engineering practice ?
Thanks for this, was easily the best and easiest solution after a lot of searching!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.