My Repository Tests use an in memory database that clears the context after each test run:
using System;
using System.Threading.Tasks;
using Application;
using Microsoft.EntityFrameworkCore;
using NUnit.Framework;
namespace UnitTests.Repositories;
public class RepositoryTestBase
{
protected readonly ApplicationDbContext _context;
public RepositoryTestBase()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: "ApplicationDbContext").Options;
_context = new ApplicationDbContext(options);
}
[TearDown]
public async Task AfterEach()
{
_context.ChangeTracker.Clear();
await _context.Database.EnsureDeletedAsync();
await _context.Database.EnsureCreatedAsync();
}
[OneTimeTearDown]
public void AfterAll()
{
_context.Dispose();
}
}
Here is the repository test that is failing, again if I run this test on its own it passes, and only fails when running all of my repository tests together:
[Test]
public async Task ReplaceItemTags_EmptyFirst()
{
var originalTags = TestData.AlexItemA.Tags;
await _context.Items.AddAsync(TestData.AlexItemA);
await _unitOfWork.CompleteAsync();
var result = await _ItemRepository.ReplaceItemTags(TestData.AlexItemA.Id, new List<Tag>() {TestData.TagB});
//TESTS FAIL HERE, THIS IS A WRAPPER AROUND _context.SaveChangesAsync()
await _unitOfWork.CompleteAsync();
Assert.NotNull(result);
Assert.AreNotEqual(originalTags, result?.Tags);
Assert.AreEqual(1, result?.Tags?.Count);
Assert.Contains(TestData.TagB, result?.Tags?.ToList());
}
Here is the method that is being tested:
public async Task<Item?> ReplaceItemTags(Guid ItemId, List<Tag> tags)
{
var Item = await _context.Items.FindAsync(ItemId);
if (Item == null)
{
return null;
}
Item.Tags = tags.Distinct().ToList();
return Item;
}
Here is the stack trace from the test failure:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException : Attempted to update or delete an entity that does not exist in the store.
at Microsoft.EntityFrameworkCore.InMemory.Storage.Internal.InMemoryTable`1.Update(IUpdateEntry entry)
at Microsoft.EntityFrameworkCore.InMemory.Storage.Internal.InMemoryStore.ExecuteTransaction(IList`1 entries, IDiagnosticsLogger`1 updateLogger)
at Microsoft.EntityFrameworkCore.InMemory.Storage.Internal.InMemoryDatabase.SaveChangesAsync(IList`1 entries, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Application.Repositories.UnitOfWork.CompleteAsync() in C:\src\EasyForum.API\Application\Repositories\UnitOfWork.cs:line 20
at UnitTests.Repositories.ItemRepositoryTests.ReplaceItemTags_EmptyFirst() in C:\src\EasyForum.API\UnitTests\Repositories\ItemRepositoryTests.cs:line 226
at NUnit.Framework.Internal.TaskAwaitAdapter.GenericAdapter`1.GetResult()
at NUnit.Framework.Internal.AsyncToSyncAdapter.Await(Func`1 invoke)
at NUnit.Framework.Internal.Commands.TestMethodCommand.RunTestMethod(TestExecutionContext context)
at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute(TestExecutionContext context)
at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.<>c__DisplayClass1_0.<Execute>b__0()
at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)
I think it may have to do with my static TestData
class. Somehow the tracking on the instances of my models are not getting reset. I have tried to detatch all tracking at teardown as well via the following code:
foreach (var entity in _context.ChangeTracker.Entries())
{
entity.State = EntityState.Detached;
}
If anyone has any ideas please let me know. Perhaps there is a better way to implement an in memory database for my testing purposes.
CodePudding user response:
Found the solution.
Tags are a database entity, I need to make sure they are stored in the database before adding them as a foreign key in the Item.