如何为创建数据库PlayersBadge条目的Void函数编写单元测试?
为你的Badge函数编写单元测试的思路与示例
看起来你卡在了如何为这个徽章创建函数写单元测试上,别担心,我们可以通过隔离数据库依赖的方式来轻松完成测试。核心思路是用模拟(Mock)对象替代真实的EliminationDbContext,这样测试不用连接真实数据库,运行更快也更可靠。
首先,先把你的函数补全(假设最后会添加到上下文并保存):
public void Badge(int pID, int bID, int gID = 0) { var list = EliminationDbContext.PlayerBadges .Where(x => x.Badge.BadgeID.Equals(bID) && x.Player.PlayerID.Equals(pID)); // 若玩家未拥有该徽章则创建新徽章 if (!list.Any()) { PlayerBadge b = new PlayerBadge { PlayerID = pID, BadgeID = bID, DateEarned = DateTime.Today, GameID = gID }; EliminationDbContext.PlayerBadges.Add(b); EliminationDbContext.SaveChanges(); } }
接下来,我们用xUnit(测试框架)和Moq(模拟框架)来写测试。首先确保你的类是依赖注入DbContext的(如果不是,建议重构一下,这样测试更友好):
public class BadgeService { private readonly EliminationDbContext _dbContext; public BadgeService(EliminationDbContext dbContext) { _dbContext = dbContext; } // 你的Badge方法放在这里 }
测试场景1:玩家已拥有该徽章,不创建新条目
这个测试要验证当玩家已经有目标徽章时,函数不会执行添加和保存操作:
using Xunit; using Moq; using Microsoft.EntityFrameworkCore; using System.Linq; public class BadgeServiceTests { [Fact] public void Badge_PlayerAlreadyHasBadge_DoesNotCreateNewEntry() { // 准备模拟数据:玩家1已经拥有徽章100 var existingBadges = new List<PlayerBadge> { new PlayerBadge { Player = new Player { PlayerID = 1 }, Badge = new Badge { BadgeID = 100 } } }.AsQueryable(); // 模拟DbSet var mockBadgeSet = new Mock<DbSet<PlayerBadge>>(); mockBadgeSet.As<IQueryable<PlayerBadge>>() .Setup(m => m.Provider).Returns(existingBadges.Provider); mockBadgeSet.As<IQueryable<PlayerBadge>>() .Setup(m => m.Expression).Returns(existingBadges.Expression); mockBadgeSet.As<IQueryable<PlayerBadge>>() .Setup(m => m.ElementType).Returns(existingBadges.ElementType); mockBadgeSet.As<IQueryable<PlayerBadge>>() .Setup(m => m.GetEnumerator()).Returns(existingBadges.GetEnumerator()); // 模拟DbContext var mockDbContext = new Mock<EliminationDbContext>(); mockDbContext.Setup(c => c.PlayerBadges).Returns(mockBadgeSet.Object); // 创建服务实例 var service = new BadgeService(mockDbContext.Object); // 执行测试方法 service.Badge(1, 100); // 验证:没有调用Add和SaveChanges mockBadgeSet.Verify(d => d.Add(It.IsAny<PlayerBadge>()), Times.Never); mockDbContext.Verify(c => c.SaveChanges(), Times.Never); } }
测试场景2:玩家未拥有该徽章,创建新条目
这个测试要验证当玩家没有目标徽章时,函数会创建正确的PlayerBadge对象并保存到数据库:
[Fact] public void Badge_PlayerDoesNotHaveBadge_CreatesNewEntry() { // 准备模拟数据:空的徽章列表 var existingBadges = new List<PlayerBadge>().AsQueryable(); // 模拟DbSet var mockBadgeSet = new Mock<DbSet<PlayerBadge>>(); mockBadgeSet.As<IQueryable<PlayerBadge>>() .Setup(m => m.Provider).Returns(existingBadges.Provider); mockBadgeSet.As<IQueryable<PlayerBadge>>() .Setup(m => m.Expression).Returns(existingBadges.Expression); mockBadgeSet.As<IQueryable<PlayerBadge>>() .Setup(m => m.ElementType).Returns(existingBadges.ElementType); mockBadgeSet.As<IQueryable<PlayerBadge>>() .Setup(m => m.GetEnumerator()).Returns(existingBadges.GetEnumerator()); // 模拟DbContext var mockDbContext = new Mock<EliminationDbContext>(); mockDbContext.Setup(c => c.PlayerBadges).Returns(mockBadgeSet.Object); // 创建服务实例 var service = new BadgeService(mockDbContext.Object); var today = DateTime.Today; // 执行测试方法(玩家2,徽章200,游戏ID5) service.Badge(2, 200, 5); // 验证:调用了Add方法,且参数是符合预期的PlayerBadge对象 mockBadgeSet.Verify(d => d.Add(It.Is<PlayerBadge>(pb => pb.PlayerID == 2 && pb.BadgeID == 200 && pb.DateEarned == today && pb.GameID == 5)), Times.Once); // 验证:调用了一次SaveChanges mockDbContext.Verify(c => c.SaveChanges(), Times.Once); }
关键注意点
- 如果你的
PlayerBadge类没有直接暴露PlayerID和BadgeID字段,而是通过导航属性Player和Badge访问,那模拟数据时要确保这些导航属性被正确初始化(就像第一个测试里那样)。 - 单元测试的核心是覆盖核心逻辑分支:这里就是“已有徽章”和“无徽章”两个分支,确保每个分支的行为都符合预期。
- 如果你之前没有用依赖注入,建议重构你的类,让
EliminationDbContext通过构造函数传入,这样才能方便地替换为模拟对象。
内容的提问来源于stack exchange,提问作者hmad
相关产品推荐
相关产品推荐

