Xunit - 如何使用 Moq 和 EF Core 作为身份主键
Xunit - How to use Moq and EF Core for Identity Primary Key
我正在尝试使用 AutoMoq 和 Xunit 自动执行单元测试以插入功能。
但我不断了解到无法将值插入到 KeyColumn 中,如下所示。 EnrolmentRecordID
是我的 SQL 数据库中的 IdentityColumn,它的值是在插入时自动生成的。
Message: Microsoft.EntityFrameworkCore.DbUpdateException : An error
occurred while updating the entries. See the inner exception for
details.
---- System.Data.SqlClient.SqlException : Cannot insert explicit value for identity column in table 'EN_Schedules' when IDENTITY_INSERT is
set to OFF.
如果我不使用 Moq 或不将数据设置到 EnrolmentRecordID
列,则可以避免。但我不知道如何在 AutoMoq 中排除 EnrolmentRecordID
。因为它是关键列,所以我也不能为该列设置 NULLABLE 特性。
StudentSchedule.cs
public class StudentSchedule
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EnrolmentRecordID { get; set; }
public string AcademicYearID { get; set; }
[Display(Name = "Student")]
public string StudentName { get; set; }
public string ProposedQual { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime? DateCreated { get; set; }
}
添加服务
public async Task Add(StudentSchedule model)
{
await _context.Schedules.AddAsync(model);
await _context.SaveChangesAsync();
}
XUnitTest
public class TestCommandsSchedule
{
private ERAppData.Commands.CommandSchedule _command;
public TestCommandsSchedule()
{
_command = new ERAppData.Commands.CommandSchedule(AppsecDBContext.GenerateAppsecDBContext() as ERAppData.DbContexts.AppsecDbContext);
}
[Theory]
[AutoMoqData]
public async Task Should_Add_Schedule(StudentSchedule model)
{
model.AcademicYearID = "16/17";
model.DateCreated = null;
await _command.Add(model);
Assert.True(model.EnrolmentRecordID > 0);
}
}
能否请您帮助我如何使用 Moq 生成 MockObject
并测试 Add
服务?谢谢。
这个简化的示例展示了如何将被测对象与具体对象分离,以便它可以单独进行单元测试。
摘掉 DbContext
public interface IStudenScheduleService : IGenericRepository<StudentSchedule> {
}
public interface IGenericRepository<T> {
Task<T> AddAsync(T value, CancellationToken cancellationToken = default(CancellationToken));
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
}
确保实现包装实际上下文并提供所需的功能。
让主题class依赖抽象。
public class CommandSchedule {
private readonly IStudenScheduleService _context;
public CommandSchedule(IStudenScheduleService context) {
this._context = context;
}
public async Task Add(StudentSchedule model) {
await _context.AddAsync(model);
await _context.SaveChangesAsync();
}
}
有了这个,被测对象的依赖关系就可以被模拟并用于练习测试。
[Theory]
[AutoMoqData]
public async Task Should_Add_Schedule(StudentSchedule model)
//Arrange
var expectedId = 0;
var expectedDate = DateTime.Now;
var context = new Mock<IStudenScheduleService>();
context.Setup(_ => _.SaveChangesAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(1)
.Callback(() => {
model.EnrolmentRecordID = ++expectedId;
model.DateCreated = expectedDate;
})
.Verifiable();
context.Setup(_ => _.AddAsync(It.IsAny<StudentSchedule>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StudentSchedule m, CancellationToken t) => m)
.Verifiable();
var _command = new CommandSchedule(context.Object);
model.AcademicYearID = "16/17";
model.DateCreated = null;
//Act
await _command.Add(model);
//Assert
context.Verify();
Assert.AreEqual(expectedId, model.EnrolmentRecordID);
Assert.AreEqual(expectedDate, model.DateCreated);
}
我正在尝试使用 AutoMoq 和 Xunit 自动执行单元测试以插入功能。
但我不断了解到无法将值插入到 KeyColumn 中,如下所示。 EnrolmentRecordID
是我的 SQL 数据库中的 IdentityColumn,它的值是在插入时自动生成的。
Message: Microsoft.EntityFrameworkCore.DbUpdateException : An error occurred while updating the entries. See the inner exception for details. ---- System.Data.SqlClient.SqlException : Cannot insert explicit value for identity column in table 'EN_Schedules' when IDENTITY_INSERT is set to OFF.
如果我不使用 Moq 或不将数据设置到 EnrolmentRecordID
列,则可以避免。但我不知道如何在 AutoMoq 中排除 EnrolmentRecordID
。因为它是关键列,所以我也不能为该列设置 NULLABLE 特性。
StudentSchedule.cs
public class StudentSchedule
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EnrolmentRecordID { get; set; }
public string AcademicYearID { get; set; }
[Display(Name = "Student")]
public string StudentName { get; set; }
public string ProposedQual { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime? DateCreated { get; set; }
}
添加服务
public async Task Add(StudentSchedule model)
{
await _context.Schedules.AddAsync(model);
await _context.SaveChangesAsync();
}
XUnitTest
public class TestCommandsSchedule
{
private ERAppData.Commands.CommandSchedule _command;
public TestCommandsSchedule()
{
_command = new ERAppData.Commands.CommandSchedule(AppsecDBContext.GenerateAppsecDBContext() as ERAppData.DbContexts.AppsecDbContext);
}
[Theory]
[AutoMoqData]
public async Task Should_Add_Schedule(StudentSchedule model)
{
model.AcademicYearID = "16/17";
model.DateCreated = null;
await _command.Add(model);
Assert.True(model.EnrolmentRecordID > 0);
}
}
能否请您帮助我如何使用 Moq 生成 MockObject
并测试 Add
服务?谢谢。
这个简化的示例展示了如何将被测对象与具体对象分离,以便它可以单独进行单元测试。
摘掉 DbContext
public interface IStudenScheduleService : IGenericRepository<StudentSchedule> {
}
public interface IGenericRepository<T> {
Task<T> AddAsync(T value, CancellationToken cancellationToken = default(CancellationToken));
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
}
确保实现包装实际上下文并提供所需的功能。
让主题class依赖抽象。
public class CommandSchedule {
private readonly IStudenScheduleService _context;
public CommandSchedule(IStudenScheduleService context) {
this._context = context;
}
public async Task Add(StudentSchedule model) {
await _context.AddAsync(model);
await _context.SaveChangesAsync();
}
}
有了这个,被测对象的依赖关系就可以被模拟并用于练习测试。
[Theory]
[AutoMoqData]
public async Task Should_Add_Schedule(StudentSchedule model)
//Arrange
var expectedId = 0;
var expectedDate = DateTime.Now;
var context = new Mock<IStudenScheduleService>();
context.Setup(_ => _.SaveChangesAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(1)
.Callback(() => {
model.EnrolmentRecordID = ++expectedId;
model.DateCreated = expectedDate;
})
.Verifiable();
context.Setup(_ => _.AddAsync(It.IsAny<StudentSchedule>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StudentSchedule m, CancellationToken t) => m)
.Verifiable();
var _command = new CommandSchedule(context.Object);
model.AcademicYearID = "16/17";
model.DateCreated = null;
//Act
await _command.Add(model);
//Assert
context.Verify();
Assert.AreEqual(expectedId, model.EnrolmentRecordID);
Assert.AreEqual(expectedDate, model.DateCreated);
}