如何让Mock在Setup未精确匹配时返回null?默认设置无效求解决
问题
我在测试时希望Moq在未匹配到Setup的调用时返回null,但目前它总是返回带默认值的Mock对象。尝试过mock.SetReturnsDefault(null)和mock.DefaultValue = null都没效果。
测试代码如下:
[Theory] [InlineData("DFF0DF04-0D3F-419B-82FB-23F0A8E2452C", 1)] [InlineData("DFF0DF04-0D3F-419B-82FB-23F0A8E2452C", 2)] [InlineData("985F7161-1B0F-4EEA-9572-02D6D13D712B", 1)] public async Task Get_ShouldReturn1TierSet_GivenAProgram_And_AnExistingId(Guid programId, int id) { _tierSetService.Setup(x => x.GetTierSet(1, new Guid("DFF0DF04-0D3F-419B-82FB-23F0A8E2452C"))).ReturnsAsync(new TierSet() { Id = 1, StartDate = DateTime.Now, EndDate = DateTime.Now, Name = "Some Tier Set", TierSetState = Core.Constants.TierSetState.Draft, CreateDate = DateTime.Now, Program = new Domain.Models.Program(){ }, DisplayTierSet = false }); var actual = await _controller.Get(programId, id); if (programId == new Guid("DFF0DF04-0D3F-419B-82FB-23F0A8E2452C") && id == 1) { Assert.NotNull(actual); } else { Assert.Null(actual); } }
另外,我单独加了一个测试,用It.IsAny<>参数设置Mock返回null是正常工作的,但为什么默认设置就无效?怎么才能确保未匹配Setup时返回null?
原因分析
Moq的DefaultValue和SetReturnsDefault仅对完全没有Setup的成员生效。如果你已经对某个方法(比如GetTierSet)设置了至少一个匹配规则,那么所有对该方法的调用都会优先尝试匹配已有的Setup——如果没有匹配上,Moq会自动生成该方法返回类型的默认实例(引用类型是空Mock对象,值类型是默认值),而不会触发全局的默认返回设置。
你用It.IsAny<>设置返回null能生效,是因为这个Setup覆盖了所有对GetTierSet的调用,相当于一个“兜底”的匹配规则,而不是依赖全局默认值。
解决方案
要实现“精确匹配的Setup返回指定值,其他调用返回null”,有两种可靠方式:
添加兜底的Setup规则
在你的精确匹配Setup之后,添加一个覆盖所有参数的Setup,明确返回null:// 精确匹配的Setup _tierSetService.Setup(x => x.GetTierSet(1, new Guid("DFF0DF04-0D3F-419B-82FB-23F0A8E2452C"))) .ReturnsAsync(new TierSet() { /* ... */ }); // 兜底规则:所有其他调用返回null _tierSetService.Setup(x => x.GetTierSet(It.IsAny<int>(), It.IsAny<Guid>())) .ReturnsAsync((TierSet)null);Moq会按照Setup的定义顺序匹配,先尝试精确匹配,匹配失败就会触发这个兜底规则。
使用
DefaultValueProvider自定义默认行为(针对Moq 4.13+)
如果需要全局生效,可以自定义默认值提供器,让未匹配的方法调用返回null:var mock = new Mock<ITierSetService> { DefaultValueProvider = new NullDefaultValueProvider() }; // 自定义提供器实现 public class NullDefaultValueProvider : DefaultValueProvider { protected override object GetDefaultValue(Type type, Mock mock) { return type.IsValueType ? base.GetDefaultValue(type, mock) : null; } }这种方式适合需要对多个Mock统一设置默认返回
null的场景,但注意:如果某个方法已经有至少一个Setup,依然会优先走Setup的匹配逻辑,只有完全没有Setup的方法才会用这个默认值。
内容的提问来源于stack exchange,提问作者spuppett

