.NET Core API实体唯一属性的单元测试实现方案咨询
问题描述
实体定义
[Index(nameof(Name), IsUnique = true)] public class AccountType:IEntityBase { [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public string Id { get; set; } [Required] [StringLength(100)] public string Name { get; set; } }
待测试代码
public async Task<string> CreateEntity(string entityName) { TEntity entity = new TEntity { Name = entityName }; try { //将实体保存到数据库的代码 } catch (DbUpdateException ex) when (ex.InnerException.HResult == -2147467259) { //处理异常的代码 } return entity.Id; }
需解决两个核心问题:
- 针对上述代码应当编写怎样的单元测试?
- 插入相同名称时,测试项目中未抛出
DbUpdateException(但生产环境可正常触发),该如何修复?
解决方案
一、单元测试设计
针对CreateEntity方法,需覆盖两种核心业务场景:
1. 正常创建实体测试
- 测试逻辑:传入唯一名称调用方法,验证返回非空ID,且数据库中存在对应名称的实体。
- 示例代码(基于xUnit + EF Core测试数据库):
[Fact] public async Task CreateEntity_WithUniqueName_ReturnsValidId() { // 初始化测试数据库上下文 await using var context = new YourDbContext(GetTestDbOptions()); var service = new YourService(context); // 执行方法 var entityId = await service.CreateEntity("TestAccountType"); // 断言验证 Assert.NotNull(entityId); var createdEntity = await context.AccountTypes.FindAsync(entityId); Assert.Equal("TestAccountType", createdEntity.Name); }
2. 重复名称插入的异常处理测试
- 测试逻辑:先插入一个实体,再调用方法传入同名参数,验证触发
DbUpdateException且异常处理逻辑生效。 - 示例代码:
[Fact] public async Task CreateEntity_WithDuplicateName_TriggersExceptionHandling() { await using var context = new YourDbContext(GetTestDbOptions()); var service = new YourService(context); // 预先插入同名实体 await service.CreateEntity("DuplicateType"); await context.SaveChangesAsync(); // 再次插入同名实体 var entityId = await service.CreateEntity("DuplicateType"); // 根据实际异常处理逻辑调整断言:比如返回已存在实体的ID var existingEntity = await context.AccountTypes.FirstAsync(t => t.Name == "DuplicateType"); Assert.Equal(existingEntity.Id, entityId); }
二、测试项目不抛异常的原因及修复方案
1. 内存数据库未启用约束校验
EF Core的InMemory数据库默认不强制执行唯一索引、外键等数据库层面约束,仅做模型字段校验,导致插入同名实体不会触发异常。
修复方案:改用SQLite内存数据库并开启约束校验:
private DbContextOptions<YourDbContext> GetTestDbOptions() { var options = new DbContextOptionsBuilder<YourDbContext>() .UseSqlite("DataSource=:memory:", x => x.SuppressForeignKeyEnforcement(false)) .Options; // SQLite内存数据库需手动打开连接才能保留数据 using var context = new YourDbContext(options); context.Database.OpenConnection(); context.Database.EnsureCreated(); return options; }
2. Mock的DbContext未模拟异常抛出
如果使用Moq等框架模拟DbContext,默认不会抛出数据库异常,需手动配置SaveChangesAsync抛出指定异常:
[Fact] public async Task CreateEntity_WithDuplicateName_CatchesDbUpdateException() { var mockContext = new Mock<YourDbContext>(); // 模拟唯一约束冲突的异常 var innerException = new SqlException("Unique constraint violation", -2147467259); mockContext.Setup(c => c.SaveChangesAsync(It.IsAny<CancellationToken>())) .ThrowsAsync(new DbUpdateException("Update failed", innerException)); var service = new YourService(mockContext.Object); await service.CreateEntity("DuplicateType"); // 断言异常处理逻辑已执行(根据业务需求调整) }
3. 异常过滤的HResult不匹配
不同数据库的唯一约束异常HResult/错误码不同(比如SQL Server与SQLite错误码不一致),测试用数据库与生产环境类型不同时,会导致when条件不触发。
修复方案:避免依赖硬编码的HResult,改用数据库通用的错误码判断:
catch (DbUpdateException ex) { // SQL Server唯一约束冲突错误码为2601,SQLite为19 if (ex.InnerException is SqlException sqlEx && (sqlEx.Number == 2601 || sqlEx.SqliteErrorCode == 19)) { // 处理异常的代码 } }
内容的提问来源于stack exchange,提问作者Manta
相关产品推荐
相关产品推荐

