用途
Moq 是 VanDaemon 项目中贯穿使用的模拟框架,用于单元测试。项目中的所有服务均注册为单例并依赖接口,因此非常适合被模拟。测试通过 Mock<T> 创建测试替身,用 Setup() 配置行为,用 Verify() 断言交互。
主要能力
- 创建模拟实例:
new Mock<ITankService>() - 获取被模拟的实例:
mock.Object - 配置方法行为:
.Setup(x => x.Method()) - 同步返回值:
.Returns(value) - 异步返回值:
.ReturnsAsync(value) - 匹配任意参数:
It.IsAny<CancellationToken>() - 使用谓词匹配参数:
It.Is<Guid>(g => g != Guid.Empty) - 断言方法被调用:
.Verify(x => x.Method(), Times.Once)
使用方式
基础服务模拟
// Mock a service interface
var mockTankService = new Mock<ITankService>();
mockTankService
.Setup(x => x.GetAllTanksAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<Tank> { new Tank { Id = Guid.NewGuid(), Name = "Fresh Water" } });
var controller = new TanksController(mockTankService.Object);
带参数匹配的模拟
var mockControlService = new Mock<IControlService>();
mockControlService
.Setup(x => x.SetControlStateAsync(
It.Is<Guid>(id => id != Guid.Empty),
It.IsAny<object>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
模拟 Logger(通过 ILogger 使用 Serilog)
var mockLogger = new Mock<ILogger<TankService>>();
var service = new TankService(mockLogger.Object, mockFileStore.Object);
// Verify logging occurred (Moq can't verify extension methods directly)
// Use LoggerFactory or just trust the implementation
模拟 JsonFileStore
var mockFileStore = new Mock<IJsonFileStore>();
mockFileStore
.Setup(x => x.LoadAsync<List<Tank>>("tanks.json", It.IsAny<CancellationToken>()))
.ReturnsAsync(testTanks);
模拟 SignalR Hub 上下文
var mockHubContext = new Mock<IHubContext<TelemetryHub>>();
var mockClients = new Mock<IHubClients>();
var mockGroup = new Mock<IClientProxy>();
mockHubContext.Setup(x => x.Clients).Returns(mockClients.Object);
mockClients.Setup(x => x.Group("tanks")).Returns(mockGroup.Object);
适用场景
- 编写需要隔离依赖的单元测试
- 验证方法调用
- 模拟服务接口、日志、文件存储、SignalR Hub 上下文等依赖
限制
- Moq 无法直接验证扩展方法(如日志扩展方法),需改用 LoggerFactory 或信任实现。
相关技能
- 测试结构与断言参见 xunit 技能
- 可读断言参见 fluent-assertions 技能
- 与模拟配合使用的 async/await 模式参见 csharp 技能