如何为无返回值的MassTransit消费者编写单元测试并解决验证问题
无返回值MassTransit消费者的单元测试方案
消费者代码
public async Task Consume(ConsumeContext<CreateEvent> context) { var existing = await _dbContext.Mytable.SingleOrDefaultAsync(c => c.Id.Equals(context.Message.Id)); if (existing == null) { var newData = new Table { Id = context.Message.Id, Description = context.Message.Description, }; await _dbContext.table.AddAsync(newData); await _dbContext.SaveChangesAsync(); } _logger.LogInformation($"{context.Message.Id.ToUpperInvariant()} already exists."); await Task.CompletedTask; }
需要覆盖的测试场景
- 当消息对应的数据库记录不存在时:
- 验证
_dbContext.table.AddAsync被调用,且传入的实体参数匹配消息内容 - 验证
_dbContext.SaveChangesAsync被调用 - 验证日志输出对应提示信息
- 验证
- 当消息对应的数据库记录已存在时:
- 验证
_dbContext.table.AddAsync未被调用 - 验证
_dbContext.SaveChangesAsync未被调用 - 验证日志输出对应提示信息
- 验证
- 确保消费者方法能正常执行完毕,无异常抛出
当前测试代码的问题解决
你遇到的dbContext.Verify()报错,是因为给SaveChangesAsync设置了Verifiable(),但如果测试的是「记录已存在」场景,这个方法根本不会被调用,导致验证失败。需要分场景调整测试逻辑:
记录不存在的场景:
// 模拟数据库无对应记录 mysampleEntities.Clear(); dbContext.Setup(r => r.Mytable).ReturnsDbSet(mysampleEntities); dbContext.Setup(r => r.table.AddAsync(It.IsAny<Table>(), It.IsAny<CancellationToken>())).Returns(Task.CompletedTask); dbContext.Setup(r => r.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1).Verifiable(); // 执行消费者逻辑 await consumer.Consume(contextMock.Object); // 验证调用次数和参数 dbContext.Verify(r => r.table.AddAsync(It.Is<Table>(t => t.Id == message.Id && t.Description == message.Description), It.IsAny<CancellationToken>()), Times.Once); dbContext.Verify(); // 此时验证通过记录已存在的场景:
// 模拟数据库已有对应记录 var existingRecord = new Table { Id = message.Id }; mysampleEntities.Add(existingRecord); dbContext.Setup(r => r.Mytable).ReturnsDbSet(mysampleEntities); // 执行消费者逻辑 await consumer.Consume(contextMock.Object); // 验证方法未被调用 dbContext.Verify(r => r.table.AddAsync(It.IsAny<Table>(), It.IsAny<CancellationToken>()), Times.Never); dbContext.Verify(r => r.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Never);
关于await Task.CompletedTask的测试
这行代码仅用于满足异步方法的语法要求,返回一个已完成的任务。不需要单独测试——只要你能正常await consumer.Consume(...)且方法未抛出异常,就说明代码顺利执行到了末尾,这行逻辑已被覆盖。
内容的提问来源于stack exchange,提问作者jubi
相关产品推荐
相关产品推荐

