I am using ASP.NET Core 2.2, EF Core and MOQ. As you can see in the following code, I have two tests, and running both together, with both database name "MovieListDatabase" I got an error in one of the tests with this message:
Message: System.ArgumentException : An item with the same key has already
been added. Key: 1
If I run each one separately they both pass.
And also, having a different database name in both tests, like "MovieListDatabase1" and "MovieListDatabase2" and running both together it pass again.
I have two questions: Why does this happen? and how can I refactor my code to re-use the in-memory database in both tests and make my test to look a bit cleaner?
public class MovieRepositoryTest
{
[Fact]
public void GetAll_WhenCalled_ReturnsAllItems()
{
var options = new DbContextOptionsBuilder<MovieDbContext>()
.UseInMemoryDatabase(databaseName: "MovieListDatabase")
.Options;
// Insert seed data into the database using one instance of the context
using (var context = new MovieDbContext(options))
{
context.Movies.Add(new Movie { Id = 1, Title = "Movie 1", YearOfRelease = 2018, Genre = "Action" });
context.Movies.Add(new Movie { Id = 2, Title = "Movie 2", YearOfRelease = 2018, Genre = "Action" });
context.Movies.Add(new Movie { Id = 3, Title = "Movie 3", YearOfRelease = 2019, Genre = "Action" });
context.SaveChanges();
}
// Use a clean instance of the context to run the test
using (var context = new MovieDbContext(options))
{
var sut = new MovieRepository(context);
//Act
var movies = sut.GetAll();
//Assert
Assert.Equal(3, movies.Count());
}
}
[Fact]
public void Search_ValidTitlePassed_ReturnsOneMovie()
{
var filters = new MovieFilters { Title = "Movie 1", YearOfRelease = 2018, Genre = "Action" };
var options = new DbContextOptionsBuilder<MovieDbContext>()
.UseInMemoryDatabase(databaseName: "MovieListDatabase")
.Options;
// Insert seed data into the database using one instance of the context
using (var context = new MovieDbContext(options))
{
context.Movies.Add(new Movie { Id = 1, Title = "Movie 1", YearOfRelease = 2018, Genre = "Action" });
context.Movies.Add(new Movie { Id = 2, Title = "Movie 2", YearOfRelease = 2018, Genre = "Action" });
context.Movies.Add(new Movie { Id = 3, Title = "Movie 3", YearOfRelease = 2019, Genre = "Action" });
context.SaveChanges();
}
// Use a clean instance of the context to run the test
using (var context = new MovieDbContext(options))
{
var sut = new MovieRepository(context);
//Act
//var movies = _sut.Search(_filters);
var movies = sut.Search(filters);
//Assert
Assert.Single(movies);
}
}
}
And this is the repository class
public class MovieRepository: IMovieRepository
{
private readonly MovieDbContext _moviesDbContext;
public MovieRepository(MovieDbContext moviesDbContext)
{
_moviesDbContext = moviesDbContext;
}
public IEnumerable<Movie> GetAll()
{
return _moviesDbContext.Movies;
}
public IEnumerable<Movie> Search(MovieFilters filters)
{
var title = filters.Title.ToLower();
var genre = filters.Genre.ToLower();
return _moviesDbContext.Movies.Where( p => (p.Title.Trim().ToLower().Contains(title) | string.IsNullOrWhiteSpace(p.Title))
& (p.Genre.Trim().ToLower().Contains(genre) | string.IsNullOrWhiteSpace(p.Genre))
& (p.YearOfRelease == filters.YearOfRelease | filters.YearOfRelease == null)
);
}
}
Thanks
While some users use the in-memory database for testing, this is generally discouraged; the SQLite provider in in-memory mode is a more appropriate test replacement for relational databases.
Testing EF Core Applications Testing is an important concern to almost all application types - it allows you to be sure your application works correctly, and makes it instantly known if its behavior regresses in the future.
Properties of unit database testingUnit tests can be automated, and you can script a set of database operations with the same ease as executing code; Unit tests are great for testing individual triggers, views, and sprocs.
It looks like you might want a class fixture.
When to use: when you want to create a single test context and share it among all the tests in the class, and have it cleaned up after all the tests in the class have finished.
Create a separate class to setup whatever data your tests will share, and to clean it up when the tests are finished running.
public class MovieSeedDataFixture : IDisposable
{
public MovieDbContext MovieContext { get; private set; } = new MovieDbContext();
public MovieSeedDataFixture()
{
MovieContext.Movies.Add(new Movie { Id = 1, Title = "Movie 1", YearOfRelease = 2018, Genre = "Action" });
MovieContext.Movies.Add(new Movie { Id = 2, Title = "Movie 2", YearOfRelease = 2018, Genre = "Action" });
MovieContext.Movies.Add(new Movie { Id = 3, Title = "Movie 3", YearOfRelease = 2019, Genre = "Action" });
MovieContext.SaveChanges();
}
public void Dispose()
{
MovieContext.Dispose();
}
}
Then use it in your tests by extending the IClassFixture<T>
interface.
public class UnitTests : IClassFixture<MovieSeedDataFixture>
{
MovieSeedDataFixture fixture;
public UnitTests(MovieSeedDataFixture fixture)
{
this.fixture = fixture;
}
[Fact]
public void TestOne()
{
// use fixture.MovieContext in your tests
}
}
You can resolve the issue by appending the timestamp with the name of database name.
var myDatabaseName = "mydatabase_"+DateTime.Now.ToFileTimeUtc();
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseInMemoryDatabase(databaseName: myDatabaseName )
.Options;
Only one database with given name is created in memory. (Documentation) Hence if you have same name this kind of exception may occur.
Similar discussion is there on this thread:
optionsBuilder.UseInMemoryDatabase("MyDatabase");
This creates/uses a database with the name “MyDatabase”. If UseInMemoryDatabase is called again with the same name, then the same in-memory database will be used, allowing it to be shared by multiple context instances.
And this github issue also suggests the same approach to add a unique string with database name Hope this helps.
Thanks, I did some changes in the fixture class and is working fine, even when I run both tests together.
Here is the change:
public class MovieSeedDataFixture : IDisposable
{
public MovieDbContext MovieContext { get; private set; }
public MovieSeedDataFixture()
{
var options = new DbContextOptionsBuilder<MovieDbContext>()
.UseInMemoryDatabase("MovieListDatabase")
.Options;
MovieContext = new MovieDbContext(options);
MovieContext.Movies.Add(new Movie { Id = 1, Title = "Movie 1", YearOfRelease = 2018, Genre = "Action" });
MovieContext.Movies.Add(new Movie { Id = 2, Title = "Movie 2", YearOfRelease = 2018, Genre = "Action" });
MovieContext.Movies.Add(new Movie { Id = 3, Title = "Movie 3", YearOfRelease = 2019, Genre = "Action" });
MovieContext.SaveChanges();
}
public void Dispose()
{
MovieContext.Dispose();
}
}
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