如何使用Moq和xUnit测试泛型仓储模式下的业务逻辑方法
问题根因
你测试拿到null值是两个逻辑完全错位的操作导致的:
- 你在测试中新建
Mock<IFolderService>实例、并为其方法配置返回值的操作完全无效:这个mock对象和你要测试的真实_sut(也就是FolderManager实例)没有任何引用关联,你给mock配置的返回值只会作用在这个独立的mock对象上,完全不会影响真实SUT的执行逻辑。 - 真实SUT的
GetFoldersByUserId方法内部,实际依赖的是IGenericDal<Folder>的GetAllByFilter方法来获取数据;你从未为这个依赖方法配置返回值,Moq框架对未配置的方法默认返回null,这就是最终拿到null结果的直接原因。
正确的测试实现步骤
- 完全删除冗余的
Mock<IFolderService>相关代码——单元测试的核心是验证真实SUT(被测类)的逻辑,不需要、也不应该Mock被测类自身的接口。 - 为SUT真实调用的下游依赖(也就是
_folderRepoMock对应的IGenericDal<Folder>.GetAllByFilter方法)配置预设返回值,让它返回你准备的测试样本数据。 - 补充空值校验,避免出现null引用的运行时错误。
修正后的测试代码示例
public class FolderServiceTest { private readonly FolderManager _sut; private readonly Mock<IGenericDal<Folder>> _folderRepoMock = new Mock<IGenericDal<Folder>>(); private readonly Mock<IFolderDal> _folderDalMock = new Mock<IFolderDal>(); public FolderServiceTest() { _sut = new FolderManager(_folderDalMock.Object, _folderRepoMock.Object); } [Fact] public async Task GetFoldersByUserId_ShouldReturnListOfFolders_WhenUserExist() { //Arrange // 直接为SUT真实调用的依赖方法打桩,匹配任意传入的过滤表达式 var sampleFolders = GetSampleFolder(); _folderRepoMock .Setup(dal => dal.GetAllByFilter(It.IsAny<Expression<Func<Folder, bool>>>())) .ReturnsAsync(sampleFolders); var expected = sampleFolders; //Act var actual = await _sut.GetFoldersByUserId(1); //Assert Assert.NotNull(actual); Assert.Equal(expected.Count, actual.Count); for (int i = 0; i < expected.Count; i++) { Assert.Equal(expected[i].FolderName, actual[i].FolderName); Assert.Equal(expected[i].Size, actual[i].Size); } // 可选的严谨性校验:确认依赖方法被正确调用了一次 _folderRepoMock.Verify( dal => dal.GetAllByFilter(It.IsAny<Expression<Func<Folder, bool>>>()), Times.Once); } // 预先定义的测试数据生成方法 private List<Folder> GetSampleFolder() => new List<Folder> { new Folder { FolderName = "工作目录", Size = 1024, AppUserId = 1, IsDeleted = false, ParentFolderId = null }, new Folder { FolderName = "个人资料", Size = 2048, AppUserId = 1, IsDeleted = false, ParentFolderId = null } }; }
额外注意点
- 打桩带表达式参数的方法时,需要提前引入
using System.Linq.Expressions;命名空间,否则会出现编译错误。如果不需要严格校验过滤表达式的具体内容,可以直接用It.IsAny<Expression<Func<Folder,bool>>>()匹配任意传入的条件;如果需要验证过滤逻辑的正确性,可以提取表达式的参数做校验,比如确认表达式中确实包含IsDeleted == false、ParentFolderId == null、AppUserId == 传入的用户id的逻辑。 - 测试过程中不需要给
IFolderDal做任何配置,因为当前测试的GetFoldersByUserId方法完全没有用到这个依赖,Moq默认返回的null值不会影响测试结果。
内容的提问来源于stack exchange,提问作者Oguzhan Can
相关产品推荐
相关产品推荐

