I'm writing a unit test to test a controller action that updates an EF core entity.
I am using SQLLite, rather than mocking.
I set up my database like this:
internal static ApplicationDbContext GetInMemoryApplicationIdentityContext()
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlite(connection)
.Options;
var context = new ApplicationDbContext(options);
context.Database.EnsureCreated();
return context;
and then add an entity to the database like this:
private DiaryEntriesController _controller;
private ApplicationDbContext _context;
[SetUp]
public void SetUp()
{
_context = TestHelperMethods.GetInMemoryApplicationIdentityContext();
_controller = new DiaryEntriesController(_context);
}
[Test]
[Ignore("http://stackoverflow.com/questions/42138960/preventing-tracking-issues-when-using-ef-core-sqllite-in-unit-tests")]
public async Task EditPost_WhenValid_EditsDiaryEntry()
{
// Arrange
var diaryEntry = new DiaryEntry
{
ID = 1,
Project = new Project { ID = 1, Name = "Name", Description = "Description", Customer = "Customer", Slug = "slug" },
Category = new Category { ID = 1, Name = "Category" },
StartDateTime = DateTime.Now,
EndDateTime = DateTime.Now,
SessionObjective = "objective",
Title = "Title"
};
_context.DiaryEntries.Add(diaryEntry);
await _context.SaveChangesAsync();
var model = AddEditDiaryEntryViewModel.FromDiaryEntryDataEntity(diaryEntry);
model.Actions = "actions";
// Act
var result = await _controller.Edit(diaryEntry.Project.Slug, diaryEntry.ID, AddEditDiaryEntryViewModel.FromDiaryEntryDataEntity(diaryEntry)) as RedirectToActionResult;
// Assert
var retreivedDiaryEntry = _context.DiaryEntries.First();
Assert.AreEqual(model.Actions, retreivedDiaryEntry.Actions);
}
My controller method looks like this:
[HttpPost]
[ValidateAntiForgeryToken]
[Route("/projects/{slug}/DiaryEntries/{id}/edit", Name = "EditDiaryEntry")]
public async Task<IActionResult> Edit(string slug, int id, [Bind("ID,CategoryID,EndDate,EndTime,SessionObjective,StartDate,StartTime,Title,ProjectID,Actions,WhatWeDid")] AddEditDiaryEntryViewModel model)
{
if (id != model.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
var diaryEntryDb = model.ToDiaryEntryDataEntity();
_context.Update(diaryEntryDb);
await _context.SaveChangesAsync();
return RedirectToAction("Details", new { slug = slug, id = id });
}
ViewData["CategoryID"] = new SelectList(_context.Categories, "ID", "Name", model.CategoryID);
ViewData["ProjectID"] = new SelectList(_context.Projects, "ID", "Customer", model.ProjectID);
return View(model);
}
My problem is that when the test runs, it errors when I try to update the entity. I get the message:
The instance of entity type 'DiaryEntry' cannot be tracked because another instance of this type with the same key is already being tracked.
The code works fine in real life. I am stuck as to how to stop the tracking after my insert in the test so that the db context that is in the production code is not still tracking the inserted entity.
I understand the benefits of mocking an interface to a repo pattern but I'd really like to get this method of testing working - where we insert data into an an-memory db and then test that it has been updated in the db.
Any help would be much appreciated.
Thanks
EDIT: I added the full code of my test to show that I'm using the same context to create the database and insert the diary entry that I instantiated the controller with.