使用XUnit与Moq实现更新操作失败,请求技术协助
问题排查与解决方案
咱们来一步步拆解你遇到的核心问题:调用SetPortfolioCompanyStatus返回null以及更新操作未生效,根源都出在Moq的匹配逻辑和测试代码的细节上。
1. 为什么返回null?Moq匹配失败了!
你在测试里给UpdatePortfolioCompany做的Setup是这样的:
mockUserRepository.Setup(m => m.UpdatePortfolioCompany(moqData.portfolioCompanies[0], It.IsAny<CancellationToken>())) .ReturnsAsync(moqData.portfolioCompanies[0]);
Moq默认是按对象引用匹配的——只有当传入的参数和moqData.portfolioCompanies[0]是同一个引用时,才会返回你设置的结果。但实际调用控制器时,你传入的是一个新建的PortfolioCompany对象:
PortfolioCompany product = new PortfolioCompany() { Id = 1, Name = "Test Private Company 1",//change data DealCode = "PRIV1", IsActive = false, IsPrivate = true };
虽然新对象的Id和Mock数据里的第一个对象一致,但它是全新的引用,Moq认不出来,所以会返回默认值null。
修复方案:修改Moq的匹配规则
把Setup改成按Id匹配,而不是按引用,同时模拟真实的更新逻辑(同步属性到Mock集合):
mockUserRepository.Setup(m => m.UpdatePortfolioCompany( It.Is<PortfolioCompany>(pc => pc.Id == 1), // 匹配任意Id为1的PortfolioCompany对象 It.IsAny<CancellationToken>())) .ReturnsAsync((PortfolioCompany pc, CancellationToken ct) => { // 同步传入对象的属性到Mock集合中的对应项 var existing = moqData.portfolioCompanies.First(x => x.Id == pc.Id); existing.IsActive = pc.IsActive; existing.Name = pc.Name; existing.DealCode = pc.DealCode; existing.IsPrivate = pc.IsPrivate; // 返回更新后的对象 return existing; });
2. 更新操作未生效?Mock数据没同步!
之前的Setup只返回了单个对象,没有修改Mock集合里的原始数据,所以调用GetAllPortfolioCompanies后看不到变化。上面的修复方案里已经包含了同步Mock集合数据的逻辑,这样查询时就能看到更新后的结果了。
3. 额外的小问题:测试方法用async Task代替async void
你的测试方法是async void,这在XUnit里会导致测试无法正确等待异步操作完成,可能出现不可预测的结果。改成async Task:
[Fact] public async Task SetPortfolioCompanyStatusAsync() { // 把void改成Task // ... 剩下的测试代码不变 }
4. 控制器代码的小优化
控制器里重复调用了两次UpdatePortfolioCompany,其实没必要,调用一次即可:
public async Task<ActionResult<PortfolioCompany>> SetPortfolioCompanyStatus([FromBody] PortfolioCompany portfolioCompany, CancellationToken cancellationToken = default) { if (portfolioCompany == null) throw new ArgumentNullException(nameof(portfolioCompany)); if (portfolioCompany.Id <= 0) throw new ArgumentOutOfRangeException(nameof(portfolioCompany.Id)); var updatedCompany = await _portfolioCompanyManagement.UpdatePortfolioCompany(portfolioCompany, cancellationToken); return updatedCompany; }
修复后的测试代码示例
整合所有修改后的测试代码如下:
[Fact] public async Task SetPortfolioCompanyStatusAsync() { int targetId = 1; var mockPortfolioRepo = new Mock<IPortfolioCompanyManagement>(); var mockUserRepo = new Mock<IUserManagement>(); // Setup 获取所有数据的方法 mockPortfolioRepo.Setup(c => c.GetPortfolioCompanies(It.IsAny<CancellationToken>())) .Returns(Task.FromResult(moqData.portfolioCompanies)); // Setup 更新方法:匹配指定Id,同步属性到Mock集合 mockPortfolioRepo.Setup(m => m.UpdatePortfolioCompany( It.Is<PortfolioCompany>(pc => pc.Id == targetId), It.IsAny<CancellationToken>())) .ReturnsAsync((PortfolioCompany pc, CancellationToken ct) => { var existing = moqData.portfolioCompanies.First(x => x.Id == pc.Id); existing.IsActive = pc.IsActive; existing.Name = pc.Name; existing.DealCode = pc.DealCode; existing.IsPrivate = pc.IsPrivate; return existing; }); // 构造要更新的对象 PortfolioCompany updateRequest = new PortfolioCompany() { Id = targetId, Name = "Test Private Company 1", DealCode = "PRIV1", IsActive = false, IsPrivate = true }; var controller = new UsersController(mockUserRepo.Object, mockPortfolioRepo.Object); var updateResult = await controller.SetPortfolioCompanyStatus(updateRequest, CancellationToken.None); // 验证返回的更新对象不为空且属性正确 Assert.NotNull(updateResult.Value); Assert.False(updateResult.Value.IsActive); // 验证集合中的数据也已更新 var getAllResult = await controller.GetAllPortfolioCompanies(CancellationToken.None); var updatedItem = getAllResult.Value.First(x => x.Id == targetId); Assert.False(updatedItem.IsActive); }
这样修改后,你的测试应该就能正确返回更新后的对象,并且集合里的数据也会同步更新啦。
内容的提问来源于stack exchange,提问作者taashibhulani
相关产品推荐
相关产品推荐

