如何对继承自ModuleBase的派生类进行单元测试并模拟其Context属性
Discord.Net ModuleBase 单元测试Context劫持方案
方案说明
你当前重写Context为自动属性的方案是可行的,但还有侵入性更低、覆盖更完整的实现方式,无需修改业务模块的默认Context定义:ModuleBase<TContext>的Context本身就是虚属性,可直接赋值或通过Mock框架注入伪造实例,SocketCommandContext及其成员(如User、Guild)均可通过Mock框架构造伪造对象,同时还可以验证基类ReplyAsync等方法的调用行为。
完整测试实现示例
以Moq框架为例,测试代码如下:
private readonly Mock<IRepository> _repositoryMock = new(MockBehavior.Strict); private readonly Mock<SocketCommandContext> _contextMock = new(MockBehavior.Strict); private readonly Mock<SocketGuildUser> _mockUser = new(MockBehavior.Strict); private const ulong TestUserId = 123456789UL; [TestMethod] public async Task Should_Reply_With_Repository_Value_When_No_TargetUser_Provided() { // 配置伪造用户信息 _mockUser.Setup(u => u.Id).Returns(TestUserId); // 配置伪造执行上下文 _contextMock.Setup(c => c.User).Returns(_mockUser.Object); // 配置仓储预期行为 const string testResult = "测试返回内容"; _repositoryMock .Setup(pr => pr.GetAsync(TestUserId)) .ReturnsAsync(testResult) .Verifiable(); // Mock业务模块,CallBase保证业务方法走真实逻辑 var moduleMock = new Mock<SampleModule>(_repositoryMock.Object) { CallBase = true }; // 注入伪造上下文 moduleMock.Object.Context = _contextMock.Object; // 配置ReplyAsync调用验证规则 moduleMock.Setup(m => m.ReplyAsync(testResult, null, false, false, null, null, null, null)) .Verifiable(); // 执行测试方法 await moduleMock.Object.RunAsync(); // 验证所有预期行为均被触发 _repositoryMock.Verify(); moduleMock.Verify(); }
额外说明
- 你提供的业务代码中构造函数存在笔误:参数名
_repository和类字段重名,赋值逻辑应改为this._repository = repository - 如果需要测试指定targetUser的场景,直接传入伪造的
SocketGuildUser实例即可,无需配置Context的User属性 - 若不想使用Mock框架,也可以手动构造
DiscordSocketClient、SocketGuild等真实实例组装SocketCommandContext,但配置成本更高
内容的提问来源于stack exchange,提问作者Józef Podlecki
相关产品推荐
相关产品推荐

