如何使用MSTest结合Moq模拟EF5 Linq查询以测试AuditRepository方法
实现方案
前置依赖安装
首先安装测试所需的NuGet包:
- MSTest.TestFramework
- MSTest.TestAdapter
- Moq
- EntityFramework(版本对应你使用的EF5版本)
前提说明
我们先对代码中缺失的定义做合理假设,你可以根据自己的实际业务调整:
- 存在
Setting实体类对应配置表,结构如下:
public class Setting { public string Name { get; set; } public bool Value { get; set; } }
SQLDBContext中的settings属性为public virtual ObjectSet<Setting> settings { get; set; }AddAuditData方法返回值为void,Audit和AuditData为业务实体类SQLDBContext的AddObject、SaveChanges方法为virtual(Moq仅能模拟虚成员/接口成员)
测试逻辑实现
我们一共需要覆盖2种核心场景:
- 审计开关开启时,验证审计数据会被添加并保存
- 审计开关关闭时,验证不会执行任何添加、保存操作
完整测试代码示例
using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System.Data.Entity.Core.Objects; // EF5的ObjectContext命名空间,根据实际版本调整 using System.Linq; [TestClass] public class AuditRepositoryTests { /// <summary> /// 测试场景:审计开关开启时,正确添加保存审计数据 /// </summary> [TestMethod] public void AddAuditData_WhenAuditEnabled_IsTrue_AddsAndSavesData() { // 1. 模拟settings数据集,构造开启审计的测试数据 var testSettings = new List<Setting> { new Setting { Name = "AuditEnabled", Value = true } }.AsQueryable(); // 2. 模拟ObjectSet<Setting>,实现IQueryable接口支持Linq查询 var mockSettingSet = new Mock<ObjectSet<Setting>>(); mockSettingSet.As<IQueryable<Setting>>() .Setup(s => s.Provider) .Returns(testSettings.Provider); mockSettingSet.As<IQueryable<Setting>>() .Setup(s => s.Expression) .Returns(testSettings.Expression); mockSettingSet.As<IQueryable<Setting>>() .Setup(s => s.ElementType) .Returns(testSettings.ElementType); mockSettingSet.As<IQueryable<Setting>>() .Setup(s => s.GetEnumerator()) .Returns(testSettings.GetEnumerator()); // 3. 模拟SQLDBContext var mockContext = new Mock<SQLDBContext>(); mockContext.Setup(c => c.settings).Returns(mockSettingSet.Object); // 4. 构造被测类实例 var auditRepository = new AuditRepository(mockContext.Object); var testAuditData = new Audit(); // 5. 执行被测方法 auditRepository.AddAuditData(testAuditData); // 6. 验证结果:AddObject和SaveChanges都被调用1次 mockContext.Verify(c => c.AddObject(nameof(AuditData), It.Is<Audit>(a => a == testAuditData)), Times.Once); mockContext.Verify(c => c.SaveChanges(), Times.Once); } /// <summary> /// 测试场景:审计开关关闭时,不执行任何操作 /// </summary> [TestMethod] public void AddAuditData_WhenAuditEnabled_IsFalse_DoNothing() { // 1. 构造关闭审计的测试数据 var testSettings = new List<Setting> { new Setting { Name = "AuditEnabled", Value = false } }.AsQueryable(); // 2. 模拟ObjectSet<Setting> var mockSettingSet = new Mock<ObjectSet<Setting>>(); mockSettingSet.As<IQueryable<Setting>>() .Setup(s => s.Provider) .Returns(testSettings.Provider); mockSettingSet.As<IQueryable<Setting>>() .Setup(s => s.Expression) .Returns(testSettings.Expression); mockSettingSet.As<IQueryable<Setting>>() .Setup(s => s.ElementType) .Returns(testSettings.ElementType); mockSettingSet.As<IQueryable<Setting>>() .Setup(s => s.GetEnumerator()) .Returns(testSettings.GetEnumerator()); // 3. 模拟SQLDBContext var mockContext = new Mock<SQLDBContext>(); mockContext.Setup(c => c.settings).Returns(mockSettingSet.Object); // 4. 构造被测类并执行方法 var auditRepository = new AuditRepository(mockContext.Object); auditRepository.AddAuditData(new Audit()); // 5. 验证结果:没有调用任何添加、保存方法 mockContext.Verify(c => c.AddObject(It.IsAny<string>(), It.IsAny<object>()), Times.Never); mockContext.Verify(c => c.SaveChanges(), Times.Never); } }
常见问题说明
- 如果你实际代码中
isAuditEnabled的取值逻辑是取FirstOrDefault而不是直接返回集合,只需要调整测试数据的构造即可,模拟的IQueryable会自动处理Linq查询逻辑 - 如果
SQLDBContext的成员没有标记为virtual,可以额外定义一个ISQLDBContext接口抽取需要的成员,让SQLDBContext实现该接口,之后直接模拟ISQLDBContext即可,无需修改原有上下文类的代码
内容的提问来源于stack exchange,提问作者Sharad
相关产品推荐
相关产品推荐

