在单元测试中使用 EF Core SqlLite 时防止出现跟踪问题 [英] Preventing tracking issues when using EF Core SqlLite in Unit Tests

查看:14
本文介绍了在单元测试中使用 EF Core SqlLite 时防止出现跟踪问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个单元测试来测试更新 EF 核心实体的控制器操作.

I'm writing a unit test to test a controller action that updates an EF core entity.

我使用的是 SQLLite,而不是模拟.

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:

无法跟踪实体类型DiaryEntry"的实例,因为已在跟踪具有相同键的此类型的另一个实例.

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.

我理解模拟 repo 模式的接口的好处,但我真的很想让这种测试方法工作 - 我们将数据插入一个内存数据库,然后测试它是否已在数据库中更新.

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.

任何帮助将不胜感激.

谢谢

我添加了我的测试的完整代码,以表明我使用相同的上下文来创建数据库并插入我用来实例化控制器的日记条目.

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.

推荐答案

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

The issue is in the setup. You are using same dbcontext everywhere. Therefore while calling update, EF throws exception that entity with same key is being tracked already. The code works in production because with every request passed to controller DI generates a new instance of controller. Since controller also have DbContext in constructor, in same service scope, DI will generate new dbcontext instance too. Hence your Edit action always have a fresh dbcontext. If you are really testing out your controller then you should make sure that your controller is getting a fresh dbcontext rather than a context which was already used.

您应该更改 GetInMemoryApplicationIdentityContext 方法以返回 DbContextOptions 然后在设置阶段,将选项存储在一个字段中.每当您需要 dbcontext(在保存实体或创建控制器期间)时,请使用存储在字段中的选项新建 DbContext.这将为您提供所需的分离,并允许您像在生产中配置一样测试您的控制器.

You should change GetInMemoryApplicationIdentityContext method to return DbContextOptions then during setup phase, store the options in a field. Whenever you need dbcontext (during saving entity or creating controller), new up DbContext using the options stored in the field. That would give you desired separation and allow you to test your controller as it would be configured in production.

这篇关于在单元测试中使用 EF Core SqlLite 时防止出现跟踪问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆