Sto scrivendo un test unitario per testare un'azione del controller che aggiorna un'entità core EF.
Sto usando SQLLite, piuttosto che beffardo.
Ho impostato il mio database in questo modo:
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;
e quindi aggiungere un'entità al database in questo modo:
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);
}
Il mio metodo di controllo assomiglia a questo:
[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);
}
Il mio problema è che quando viene eseguito il test, si verifica un errore quando provo ad aggiornare l'entità. Ottengo il messaggio:
L'istanza del tipo di entità "DiaryEntry" non può essere tracciata perché un'altra traccia di questo tipo con la stessa chiave è già stata tracciata.
Il codice funziona bene nella vita reale. Sono bloccato su come interrompere il tracciamento dopo il mio inserimento nel test in modo che il contesto db che si trova nel codice di produzione non stia ancora tracciando l'entità inserita.
Capisco i vantaggi di prendere in giro un'interfaccia per un pattern pronti contro termine, ma mi piacerebbe davvero che questo metodo di test funzionasse - dove inseriamo i dati in un db an-memory e poi testiamo che sia stato aggiornato nel db.
Qualsiasi aiuto sarebbe molto apprezzato.
Grazie
EDIT: ho aggiunto il codice completo del mio test per dimostrare che sto utilizzando lo stesso contesto per creare il database e inserire la voce di diario con cui ho creato l'istanza del controller.
Il problema è nella configurazione. Stai usando lo stesso dbcontext ovunque. Pertanto, durante la chiamata all'aggiornamento, EF genera un'eccezione sul fatto che l'entità con la stessa chiave sia già stata tracciata. Il codice funziona in produzione perché con ogni richiesta passata al controller DI viene generata una nuova istanza di controller. Poiché il controller ha anche DbContext nel costruttore, nello stesso ambito del servizio, DI genererà anche una nuova istanza di dbcontext. Quindi la tua azione Edit
sempre un nuovo dbcontext. Se stai veramente testando il tuo controller, dovresti assicurarti che il tuo controller abbia un nuovo dbcontext piuttosto che un contesto che è già stato utilizzato.
È necessario modificare il metodo GetInMemoryApplicationIdentityContext
per restituire DbContextOptions
quindi durante la fase di configurazione, memorizzare le opzioni in un campo. Ogni volta che hai bisogno di dbcontext (durante il salvataggio dell'entità o della creazione del controller), aggiungi DbContext usando le opzioni memorizzate nel campo. Questo ti darebbe la separazione desiderata e ti permetterebbe di testare il tuo controller come sarebbe configurato in produzione.
Nel tuo test 'Disponi' hai creato un nuovo DiaryEntry e non hai eliminato DbContext. Nella parte "Act" del test (che sarebbe l'azione del controller) è stata creata un'altra istanza di DbContext e quindi si tenta di aggiornare lo stesso DiaryEntry. A meno che tu non giri manualmente il tracking (cosa che non farei) EF non sa quale contesto dovrebbe tracciare il DiaryEntry. Quindi l'errore.
Risposta corretta: se dovessi indovinare il colpevole sembra essere "model.ToDiaryEntryDataEntity ()". Nell'azione del controller non si ottiene l'entità dal database. Stai trasmettendo tutti i valori di quell'entità ma il tuo metodo di estensione sta creando una nuova istanza della stessa entità che è ciò che confonde l'EF. L'azione del controller "ha funzionato" solo perché il DiaryEntry appena creato non era in DbContext. Nel tuo test lo è. â € "trevorc 1 ora fa