I have migrated a web application project from .NET Core 2.1 to 3.1 (also EF Core from 2.1.1 to 3.1.0).
After the migration, some unit tests are not working anymore, throwing duplicate keys db exception.
I simulated the problem and realize that EF core with option UseInMemoryDatabase
is behaving differently in 3.1, it does not clean up the old data.
In the second test method, the People
table already contains data added from the first test, which is not happening in 2.1
Does anyone know how can I make in-memory database to be scoped to each unit test?
Here is my testing code:
AppDbContext.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace MyConsoleApp.Database
{
public class AppDbContext: DbContext
{
protected AppDbContext(DbContextOptions options) : base(options) { }
public AppDbContext(DbContextOptions<AppDbContext> options) : this((DbContextOptions)options)
{
}
public virtual DbSet<Person> Person { get; set; }
}
public class Person
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
}
AppUnitTest.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyConsoleApp.Database;
using System.Linq;
namespace MyConsoleAppTest
{
[TestClass]
public class AppUnitTest
{
public ServiceCollection Services { get; private set; }
public ServiceProvider ServiceProvider { get; protected set; }
[TestInitialize]
public void Initialize()
{
Services = new ServiceCollection();
Services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase(databaseName: "InMemoryDb"),
ServiceLifetime.Scoped,
ServiceLifetime.Scoped);
ServiceProvider = Services.BuildServiceProvider();
}
[TestMethod]
public void TestMethod1()
{
using (var dbContext = ServiceProvider.GetService<AppDbContext>())
{
dbContext.Person.Add(new Person { Id = 0, Name = "test1" });
dbContext.SaveChanges();
Assert.IsTrue(dbContext.Person.Count() == 1);
}
}
[TestMethod]
public void TestMethod2()
{
using (var dbContext = ServiceProvider.GetService<AppDbContext>())
{
dbContext.Person.Add(new Person { Id = 0, Name = "test2" });
dbContext.SaveChanges();
Assert.IsTrue(dbContext.Person.Count() == 1);
}
}
[TestCleanup]
public virtual void Cleanup()
{
ServiceProvider.Dispose();
ServiceProvider = null;
}
}
}
MyConsoleAppTest.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
<PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
<PackageReference Include="coverlet.collector" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyConsoleApp\MyConsoleApp.csproj" />
</ItemGroup>
</Project>
You can install the package via package console
Install-Package Microsoft.EntityFrameworkCore.InMemory -Version 3.1.5
https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.InMemory
The solution for me was to change the database name with unique name.
Services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()), ServiceLifetime.Scoped, ServiceLifetime.Scoped);
In this way there is a new database for each test method.
See the github issue: https://github.com/dotnet/efcore/issues/19541
I made the following Extension
method for my UnitTest
:
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddInMemoryDbContext<TDbContext>(this IServiceCollection services) where TDbContext: DbContext
=> services.AddDbContext<TDbContext>(builder
=> builder.UseInMemoryDatabase(Guid.NewGuid().ToString())
.ConfigureWarnings(w =>
{
w.Ignore(InMemoryEventId.TransactionIgnoredWarning);
w.Throw(RelationalEventId.QueryClientEvaluationWarning);
}
), ServiceLifetime.Transient);
}
The usage is very simple. Just add all of your DbContext
to your IServiceCollection
on UnitTest
setup.
...
services
.AddInMemoryDbContext<AppDbContext>()
.AddInMemoryDbContext<FooDbContext>()
.AddInMemoryDbContext<AnotherDbContext>();
...
You also need to install Microsoft.EntityFrameworkCore.InMemory (https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.InMemory)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With