在單元測試中使用EF Core SqlLite時防止跟踪問題

asp.net-core entity-framework-core

我正在編寫單元測試來測試更新EF核心實體的控制器操作。

我使用的是SQLLite,而不是嘲笑。

我像這樣設置我的數據庫:

        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;

然後將實體添加到數據庫,如下所示:

        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);
    }

我的控制器方法如下所示:

        [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);
    }

我的問題是,當測試運行時,當我嘗試更新實體時它會出錯。我收到的消息是:

無法跟踪實體類型“DiaryEntry”的實例,因為已經跟踪了具有相同密鑰的此類型的另一個實例。

代碼在現實生活中很好用。我在插入測試後如何停止跟踪,以便生產代碼中的db上下文仍然不跟踪插入的實體。

我理解模擬一個repo模式的接口的好處,但我真的想讓這種測試方法工作 - 我們將數據插入一個內存數據庫,然後測試它已經在數據庫中更新。

任何幫助將非常感激。

謝謝

編輯:我添加了我的測試的完整代碼,以表明我使用相同的上下文來創建數據庫並插入我實例化控制器的日記條目。

一般承認的答案

問題出在設置中。您到處使用相同的dbcontext。因此,在調用update時,EF拋出異常,即已經跟踪具有相同鍵的實體。代碼在生產中工作,因為傳遞給控制器DI的每個請求都會生成一個新的控制器實例。由於控制器在構造函數中也有DbContext,在相同的服務範圍內,DI也會生成新的dbcontext實例。因此,您的Edit操作始終具有新的dbcontext。如果你真的測試了你的控制器,那麼你應該確保你的控制器獲得一個新的dbcontext而不是已經使用過的上下文。

您應該更改GetInMemoryApplicationIdentityContext方法以返回DbContextOptions然後在設置階段,將選項存儲在字段中。每當您需要dbcontext(在保存實體或創建控制器期間)時,使用存儲在該字段中的選項新建DbContext。這將為您提供所需的分離,並允許您測試控制器,因為它將在生產中進行配置。


熱門答案

在您的測試'Arrange'中,您創建了一個新的DiaryEntry並且沒有丟棄您的DbContext。在測試的“Act”部分(這將是您的控制器操作)中,您創建了另一個DbContext實例,然後嘗試更新相同的DiaryEntry。除非你手動轉向跟踪(我不會這樣做),EF不知道哪個上下文應該跟踪DiaryEntry。因此錯誤。

正確答案:如果我不得不猜測罪魁禍首似乎是'model.ToDiaryEntryDataEntity()'。在您的控制器操作中,您沒有從數據庫中獲取實體。您傳遞的是該實體的所有值,但您的擴展方法是創建同一實體的新實例,這會讓EF感到困惑。您的控制器操作“工作”只是因為您新創建的DiaryEntry不在DbContext中。在你的測試中它是。 - trevorc 1小時前



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow