You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为创建数据库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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:12:35