繁体   English   中英

使用带有 inMemoryDb 的数据库上下文的 POST 方法的 C# 单元测试

[英]C# unit test for POST method that uses DB context with inMemoryDb

我使用 InMemoryDatabase 为我的反应应用程序做一些 Post 和 Gets。 我刚开始编写单元测试,但一个特别的(Post_Id_WorkEntry_shouldReturn_Ok)让我很难过,因为我抛出了这种类型的错误: The instance of entity type ' Project ' cannot be tracked because another instance with the same key value for {' Id '} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using ' DbContextOptionsBuilder.EnableSensitiveDataLogging ' to see the conflicting key values. The instance of entity type ' Project ' cannot be tracked because another instance with the same key value for {' Id '} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using ' DbContextOptionsBuilder.EnableSensitiveDataLogging ' to see the conflicting key values.

我的控制器:

    namespace Timelogger.Api.Controllers
{
    [Route("api/[controller]")]
    public class ProjectsController : Controller
    {
        private readonly ApiContext _context;

        public ProjectsController(ApiContext context)
        {
            _context = context;

        }

        [HttpGet]
        [Route("HelloWorld")]
        public string HelloWorld()
        {
            return "Hello Back!";
        }


        // GET api/projects
        [HttpGet]
        public IActionResult Get() //async
        {
            return Ok(_context.Projects.Include(x => x.WorkEntries));
    
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody] Project prj)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    Project newProject = new Project(prj.Name, prj.StartDate, prj.EndDate);
                    switch(newProject.ValidateProject())
                    {
                        case ProjectStatus.INVALID_DATE:
                            return BadRequest("Invalid dates");

                        case ProjectStatus.SUCCES:
                            _context.Projects.Add(newProject);
                            await _context.SaveChangesAsync();
                            return Ok("Added new project with ID=" + prj.Id);
                    }

                }
                catch (Exception e)
                {
                    return BadRequest(e.Message);
                }
            }
            return BadRequest("Invalid json format");
        }

        //[HttpPut("{id}")]
        [HttpPost]
        [Route("{Id:int}/WorkEntry")]
        public async Task<IActionResult> Post(int id, [FromBody] WorkEntry workEntry)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    Console.Write("WorkEntry====: " + workEntry.ToString());
                    var updatedProject = _context.Projects.AsNoTracking().Include(x => x.WorkEntries).FirstOrDefault(x => x.Id == id);
                    switch (updatedProject.ValdidateAndMergeWorkEntry(workEntry))
                    {
                        case ValidStatus.SUCCES_NEW_WORKENTRY_ADDED:
                            Console.WriteLine("SUCCES_NEW_WORKENTRY_ADDED");
                            _context.Projects.Update(updatedProject);
                            await _context.SaveChangesAsync();
                            return Ok("Work entry added for project with ID=" + updatedProject.Id);

                        case ValidStatus.SUCCES_HOURS_MERGED:
                            Console.WriteLine("SUCCES_HOURS_MERGED");
                            _context.Projects.Update(updatedProject);
                            await _context.SaveChangesAsync();
                            return Ok("Work entry merged for project with ID=" + updatedProject.Id);

                        case ValidStatus.INVALID_HOURS_ALREADY_BOOKED:
                            Console.WriteLine("ERROR_HOURS_ALREADY_BOOKED");
                            return BadRequest("Hours are already over the legal meeting");

                        case ValidStatus.INVALID_DAY:
                            Console.WriteLine("INVALID_DAY");
                            return BadRequest("Day is not valid");

                        case ValidStatus.INVALID_HOURS:
                            Console.WriteLine("INVALID_HOURS");
                            return BadRequest("Hours are not valid");
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    return BadRequest(e.Message);
                }
            }
            return BadRequest("Invalid json format");
        }

    }
}

这是我的 UT

    namespace Timelogger.Api.Tests
    {
    public class ProjectsControllerTests
    {
        ProjectsSeedDataFixture psdf = null;

        [SetUp]
        public void Init()
        {
            psdf = new ProjectsSeedDataFixture();
        }

        [TearDown]
        public void CleanUp()
        {
            psdf.ProjectsContext.Database.EnsureDeleted();
        }

        [Test]
        public void HelloWorld_ShouldReply_HelloBack()
        {
            //arrange
            ProjectsController sut = new ProjectsController(psdf.ProjectsContext);

            //act
            var actual = sut.HelloWorld();

            //assert
            Assert.AreEqual("Hello Back!", actual);
        }

        [Test]
        public void Get_shouldReturn_Ok()
        {
            //arrange
            ProjectsController sut = new ProjectsController(psdf.ProjectsContext);
            var testProject1 = new Project("e-conomic Interview", new System.DateTime(2019, 12, 1), new System.DateTime(2020, 12, 10));
            Stubs.PopulateWorkEntries(testProject1);
            psdf.ProjectsContext.Projects.Add(testProject1);
            psdf.ProjectsContext.SaveChanges();

            //act
            var actual = sut.Get();
            var okResult = actual as OkObjectResult;

            //assert
            Assert.IsNotNull(okResult);
            Assert.AreEqual(200, okResult.StatusCode);
        }

        [Test]
        public void Post_shouldReturn_Ok()
        {
            //arrange
            ProjectsController sut = new ProjectsController(psdf.ProjectsContext);
            var testProject1 = new Project("e-conomic Interview", new System.DateTime(2019, 12, 1), new System.DateTime(2020, 12, 10));
            Stubs.PopulateWorkEntries(testProject1);

            //act
            var actual = sut.Post(testProject1);
            var okResult = actual.Result as OkObjectResult;

            //assert
            Assert.IsNotNull(okResult);
            Assert.AreEqual(200, okResult.StatusCode);
            //Assert.AreEqual("Added new project with ID=1", okResult.Value);
 
        }

        [Test]
        public void Post_shouldReturn_BadRequest()
        {
            //arrange
            ProjectsController sut = new ProjectsController(psdf.ProjectsContext);
            var testProject1 = new Project("e-conomic Interview", new System.DateTime(2020, 12, 10), new System.DateTime(2019, 12, 1));
            Stubs.PopulateWorkEntries(testProject1);

            //act
            var actual = sut.Post(testProject1);
            var badRequestResult = actual.Result as BadRequestObjectResult;

            //assert
            Assert.IsNotNull(badRequestResult);
            Assert.AreEqual(400, badRequestResult.StatusCode);
            Assert.AreEqual("Invalid dates", badRequestResult.Value);
        }

        [Test]
        public void Post_Id_WorkEntry_shouldReturn_Ok()
        {
            //arrange
            ProjectsController sut = new ProjectsController(psdf.ProjectsContext);
            psdf.ProjectsContext.Database.EnsureDeleted();
            var project = new Project("e-conomic Interview", new System.DateTime(2019, 12, 1), new System.DateTime(2020, 12, 10));
            psdf.ProjectsContext.Projects.Add(project);
            psdf.ProjectsContext.SaveChanges();
            WorkEntry workEntry = new WorkEntry { id = project.WorkEntries.Count + 1, Day = new System.DateTime(2019, 12, 1), Hours = 3 };

            //act
            var actual = sut.Post(1 , workEntry);
            var okResult = actual.Result as OkObjectResult;

            //assert
            Assert.IsNotNull(okResult);
            Assert.AreEqual(200, okResult.StatusCode);
        }
    }
} 

知道如何使帖子可测试吗? 这是因为帖子中的上下文与 UT 中的上下文不同吗? 有没有可能嘲笑它?

更新:

这就是我的测试班现在的样子

    using Timelogger.Api.Controllers;
using NUnit.Framework;
using System;
using Microsoft.AspNetCore.Mvc;
using Timelogger.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Timelogger.Api.Tests
{
    [TestFixture]
    public class ProjectsControllerTests
    {
        private static DbContextOptions<ApiContext> CreateNewContextOptions()
        {
            // Create a fresh service provider, and therefore a fresh 
            // InMemory database instance.
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkInMemoryDatabase()
                .BuildServiceProvider();

            // Create a new options instance telling the context to use an
            // InMemory database and the new service provider.
            var builder = new DbContextOptionsBuilder<ApiContext>().UseInMemoryDatabase("data").UseInternalServiceProvider(serviceProvider);
            
            return builder.Options;
        }

        [Test]
        public void HelloWorld_ShouldReply_HelloBack()
        {
            using (var context = new ApiContext(CreateNewContextOptions()))
            {
                //arrange
                ProjectsController sut = new ProjectsController(context);

                //act
                var actual = sut.HelloWorld();

                //assert
                Assert.AreEqual("Hello Back!", actual);
            };
        }

        [Test]
        public void Get_shouldReturn_Ok()
        {
            using (var context = new ApiContext(CreateNewContextOptions()))
            {
                //arrange
                ProjectsController sut = new ProjectsController(context);
                var testProject1 = new Project("e-conomic Interview", new System.DateTime(2019, 12, 1), new System.DateTime(2020, 12, 10));
                Stubs.PopulateWorkEntries(testProject1);
                context.Projects.Add(testProject1);
                context.SaveChanges();

                //act
                var actual = sut.Get();
                var okResult = actual as OkObjectResult;

                //assert
                Assert.IsNotNull(okResult);
                Assert.AreEqual(200, okResult.StatusCode);
            };
        }

        [Test]
        public void Post_shouldReturn_Ok()
        {
            using(var context = new ApiContext(CreateNewContextOptions()))
            {
                //arrange
                ProjectsController sut = new ProjectsController(context);
                var testProject1 = new Project("e-conomic Interview", new System.DateTime(2019, 12, 1), new System.DateTime(2020, 12, 10));
                Stubs.PopulateWorkEntries(testProject1);

                //act
                var actual = sut.Post(testProject1);
                var okResult = actual.Result as OkObjectResult;

                //assert
                Assert.IsNotNull(okResult);
                Assert.AreEqual(200, okResult.StatusCode);
                Assert.AreEqual("Added new project with ID=1", okResult.Value);
            };
 
        }

        [Test]
        public void Post_shouldReturn_BadRequest()
        {
            using (var context = new ApiContext(CreateNewContextOptions()))
            {
                //arrange
                ProjectsController sut = new ProjectsController(context);
                var testProject1 = new Project("e-conomic Interview", new System.DateTime(2020, 12, 10), new System.DateTime(2019, 12, 1));
                Stubs.PopulateWorkEntries(testProject1);

                //act
                var actual = sut.Post(testProject1);
                var badRequestResult = actual.Result as BadRequestObjectResult;

                //assert
                Assert.IsNotNull(badRequestResult);
                Assert.AreEqual(400, badRequestResult.StatusCode);
                Assert.AreEqual("Invalid dates", badRequestResult.Value);
            };
        }

        [Test]
        public void PostIdWorkEntry_shouldReturn_Ok()
        {
            using (var context = new ApiContext(CreateNewContextOptions()))
            {
                //arrange
                ProjectsController sut = new ProjectsController(context);
                var project = new Project("e-conomic Interview", new System.DateTime(2019, 12, 1), new System.DateTime(2020, 12, 10));
                context.Projects.Add(project);
                context.SaveChanges();
                WorkEntry workEntry = new WorkEntry(new System.DateTime(2019, 12, 3), 3 );

                //act
                var actual = sut.Post(project.Id, workEntry);
                var okResult = actual.Result as OkObjectResult;

                //assert
                Assert.IsNotNull(okResult);
                Assert.AreEqual(200, okResult.StatusCode);
            };
        }

        [Test]
        public void PostIdWorkEntry_shouldReturn_BadRequestInvalidHoursSurpassed()
        {
            using (var context = new ApiContext(CreateNewContextOptions()))
            {
                //arrange
                ProjectsController sut = new ProjectsController(context);
                var project = new Project("e-conomic Interview", new System.DateTime(2019, 12, 1), new System.DateTime(2020, 12, 10));
                WorkEntry workEntry1 = new WorkEntry { id = project.WorkEntries.Count + 1, Day = new System.DateTime(2019, 12, 1), Hours = 7 };
                project.WorkEntries.Add(workEntry1);
                context.Projects.Add(project);
                context.SaveChanges();
                WorkEntry workEntry2 = new WorkEntry { id = project.WorkEntries.Count + 1, Day = new System.DateTime(2019, 12, 1), Hours = 2 };

                //act
                var actual = sut.Post(project.Id, workEntry2);
                var badRequestResult = actual.Result as BadRequestObjectResult;

                //assert
                Assert.IsNotNull(badRequestResult);
                Assert.AreEqual(400, badRequestResult.StatusCode);
                Assert.AreEqual("Hours are over the legal point", badRequestResult.Value);
            };
        }


        [Test]
        public  void PostIdWorkEntry_shouldReturn_BadRequestInvalidDay()
        {
            using (var context = new ApiContext(CreateNewContextOptions()))
            {
                //arrange
                ProjectsController sut = new ProjectsController(context);
                var project = new Project("e-conomic Interview", new System.DateTime(2019, 12, 1), new System.DateTime(2020, 12, 10));
                context.Projects.Add(project);
                context.SaveChanges();
                WorkEntry workEntry = new WorkEntry { id = project.WorkEntries.Count + 1, Day = new System.DateTime(2020, 12, 11), Hours = 2 };

                //act
                var actual = sut.Post(project.Id, workEntry);
                var badRequestResult = actual.Result as BadRequestObjectResult;

                //assert
                Assert.IsNotNull(badRequestResult);
                Assert.AreEqual(400, badRequestResult.StatusCode);
                Assert.AreEqual("Day is not valid", badRequestResult.Value);
            };
        }


    }
} 

我认为 EF 上下文正在跟踪您在测试方法中添加的项目,然后当您的控制器由于 AsNoTracking 从数据库中检索它时,它看不到它正在跟踪的同一个项目。

您可以尝试使用两种不同的上下文,这样您传递给控制器​​的上下文就不会跟踪项目。

这就是我使用内存数据库进行测试的方式:

var options = new DbContextOptionsBuilder<ApiContext>()
                .UseLazyLoadingProxies(fixture.IsLazyLoading)
                .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
                .Options;
                
Seedcontext = new ApiContext(options);
Context = new ApiContext(options);
Resultcontext = new ApiContext(options);

Seedcontext.Database.EnsureCreated();

Initializer.Seed(Seedcontext);

每个上下文都可以访问相同的数据库,但跟踪仅限于每个单独的上下文。

请确保表ID是自动添加的(自动递增允许在新记录插入表时自动生成唯一编号)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM