2017-02-09 36 views
2

我正在编写单元测试以测试更新EF核心实体的控制器操作。在单元测试中使用EF Core SqlLite时防止跟踪问题

我正在使用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'的实例无法跟踪,因为具有相同键的此类型的另一个实例已被跟踪。

该代码在现实生活中运行良好。我坚持如何在测试中插入后停止跟踪,以便生产代码中的数据库上下文不仍然跟踪插入的实体。

我明白嘲笑接口到回购模式的好处,但我真的很想获得这种测试方法 - 我们将数据插入到内存数据库中,然后测试它已更新D b。

任何帮助将不胜感激。

感谢

编辑: 我说我的测试的完整的代码表明,我使用的是相同的上下文来创建数据库,并插入我与实例化控制器的日记。

回答

1

问题出在设置中。你在任何地方都使用相同的dbcontext。因此,在调用更新时,EF会抛出异常,即具有相同键的实体已被跟踪。代码在生产环境中工作,因为传递给控制器​​DI的每个请求都会生成一个新的控制器实例。由于控制器在构造函数中也有DbContext,所以在相同的服务范围内,DI也会生成新的dbcontext实例。因此你的Edit动作总是有一个新的dbcontext。如果您真的在测试您的控制器,那么您应该确保您的控制器正在获取新的dbcontext,而不是已经使用的上下文。

您应该更改GetInMemoryApplicationIdentityContext方法以在设置阶段返回DbContextOptions,然后将选项存储在一个字段中。无论何时您需要dbcontext(在保存实体或创建控制器期间),都可以使用字段中存储的选项来新建DbContext。这将给你想要的分离,并允许你测试你的控制器,因为它将在生产中配置。

2

在您的测试'Arrange'中,您创建了一个新的DiaryEntry,并且未处理您的DbContext。在测试的'Act'部分(这将是您的控制器操作)中,您已创建另一个DbContext实例,然后尝试更新相同的DiaryEntry。除非你手动打开跟踪(我不会这样做),EF不知道哪个上下文应该跟踪DiaryEntry。因此错误。

正确答案:如果我不得不猜测罪魁祸首似乎是'model.ToDiaryEntryDataEntity()'。在您的控制器操作中,您没有从数据库中获取实体。您正在传递该实体的所有值,但您的扩展方法正在创建一个同样实体的新实例,这就是令人困惑的EF。您的控制器操作“有效”仅仅是因为您新创建的DiaryEntry不在DbContext中。在你的测试中,它是。 - trevorc 1小时前

+0

谢谢@trevorc。我编辑问题以显示我实际上在控制器中使用了与测试的排列部分相同的数据库上下文。对不起,最初没有更清楚。 –

+1

如果我不得不猜测罪魁祸首似乎是'model.ToDiaryEntryDataEntity()'。在您的控制器操作中,您没有从数据库中获取实体。您正在传递该实体的所有值,但您的扩展方法正在创建一个同样实体的新实例,这就是令人困惑的EF。您的控制器操作“有效”仅仅是因为您新创建的DiaryEntry不在DbContext中。在你的测试中,它是。 – trevorc

+0

只是另一个建议。您的控制器编辑操作应首先获取实体,然后使用扩展方法将任何更改映射到属性,然后更新当前上下文中的实体。 – trevorc