[英]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.