【问题标题】:Xunit - How to use Moq and EF Core for Identity Primary KeyXunit - 如何将 Moq 和 EF Core 用于身份主键
【发布时间】:2017-04-04 22:35:11
【问题描述】:

我正在尝试使用 AutoMoq 和 Xunit 自动执行单元测试以进行插入功能。

但我不断发现我无法在 KeyColumn 中插入值,如下所示。 EnrolmentRecordID 是我的 SQL 数据库中的 IdentityColumn,它的值是在插入时自动生成的。

消息:Microsoft.EntityFrameworkCore.DbUpdateException:错误 更新条目时发生。请参阅内部异常 细节。 ---- System.Data.SqlClient.SqlException:当 IDENTITY_INSERT 为时,无法在表“EN_Schedules”中插入标识列的显式值 设置为关闭。

如果我不使用 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 服务?谢谢。

【问题讨论】:

  • 这是一个集成测试吗?
  • 不,单元测试
  • @Nkosi 我不应该在 UnitTest 中使用真实的数据库吗?
  • 不,那么它就变成了一个集成测试
  • 显示如何设置命令。提供一个minimal reproducible example 可用于重现您的问题。

标签: c# moq entity-framework-core xunit


【解决方案1】:

这个简化的示例展示了如何将被测对象与实体分离,以便对其进行单独的单元测试。

抽象掉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));
}

确保实现包含实际上下文并提供所需的功能。

让主题类依赖于抽象。

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);
}

【讨论】:

  • 在 .Net Core 中使用 InMemoryDatabase 功能怎么样?你怎么看呢?什么是“context.Verify();”做什么?
  • @TTCG,我的偏好是在某种程度上使用抽象进行单元测试和内存数据库进行集成测试。 context.Verify 要求模拟验证所有设置是否按预期执行。您必须将设置标记为.Verifiable().Verify() 才能工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-10
  • 2012-06-29
相关资源
最近更新 更多