如何对调用CreateQuery<DynamicTableEntity>的方法进行单元测试
解决Azure Table Storage单元测试中Mock CreateQuery引发的空引用异常
问题背景
生产代码如下:
public async Task<List<MyEntity>> GetEntities() { var query = this.table.CreateQuery<DynamicTableEntity>() .Where(d => d.Properties[nameof(MyEntity.Fid)].GuidValue == Guid.Empty) .AsTableQuery(); var result = table.ExecuteQuery<DynamicTableEntity>(query); // 其他逻辑 }
单元测试时,this.table.CreateQuery().Where行触发System.NullReferenceException。尝试Mock CreateQuery返回空TableQuery:
cloudTable.Setup(s => s.CreateQuery<DynamicTableEntity>()).Returns(new TableQuery<DynamicTableEntity>());
编译通过但运行时仍抛出空引用异常,推测是Where操作访问的集合相关属性为null。
问题原因
手动创建的TableQuery<DynamicTableEntity>实例未初始化IQueryable接口所需的Provider属性,而Queryable.Where扩展方法会访问该属性,导致空引用。
解决方案
核心思路是:Mock CreateQuery返回一个具备有效IQueryable属性的TableQuery实例,同时Mock ExecuteQuery返回预设测试数据(实际业务逻辑依赖的是ExecuteQuery的结果,而非CreateQuery构建的查询对象)。
步骤1:准备测试数据
先构造符合业务场景的DynamicTableEntity列表:
// 构造测试用的DynamicTableEntity集合 var testDynamicEntities = new List<DynamicTableEntity> { new DynamicTableEntity { PartitionKey = "test-partition", RowKey = "test-row-1", Properties = new Dictionary<string, EntityProperty> { { nameof(MyEntity.Fid), new EntityProperty(Guid.Empty) } } }, // 可添加更多测试实体 }.AsQueryable();
步骤2:Mock TableQuery实例
通过Moq模拟TableQuery<DynamicTableEntity>,并实现其IQueryable接口的必要属性,避免空引用:
var mockTableQuery = new Mock<TableQuery<DynamicTableEntity>>(); // 实现IQueryable接口的核心属性 mockTableQuery.As<IQueryable<DynamicTableEntity>>() .Setup(q => q.Provider) .Returns(testDynamicEntities.Provider); mockTableQuery.As<IQueryable<DynamicTableEntity>>() .Setup(q => q.Expression) .Returns(testDynamicEntities.Expression); mockTableQuery.As<IQueryable<DynamicTableEntity>>() .Setup(q => q.ElementType) .Returns(testDynamicEntities.ElementType); mockTableQuery.As<IQueryable<DynamicTableEntity>>() .Setup(q => q.GetEnumerator()) .Returns(testDynamicEntities.GetEnumerator());
步骤3:Mock CloudTable的方法
将Mock好的TableQuery关联到CloudTable的CreateQuery方法,同时Mock ExecuteQuery返回预设数据:
var mockCloudTable = new Mock<CloudTable>(new Uri("http://localhost/test-table")); // Mock CreateQuery返回具备有效IQueryable属性的TableQuery mockCloudTable.Setup(s => s.CreateQuery<DynamicTableEntity>()) .Returns(mockTableQuery.Object); // Mock ExecuteQuery返回预设的测试数据 mockCloudTable.Setup(s => s.ExecuteQuery<DynamicTableEntity>(It.IsAny<TableQuery<DynamicTableEntity>>())) .Returns(testDynamicEntities.ToList());
简化方案(忽略查询构建细节)
如果不需要验证查询条件的正确性,可直接Mock ExecuteQuery返回数据,同时让CreateQuery返回一个不会引发空引用的Mock实例:
var mockCloudTable = new Mock<CloudTable>(new Uri("http://localhost/test-table")); // 返回Mock的TableQuery避免空引用 mockCloudTable.Setup(s => s.CreateQuery<DynamicTableEntity>()) .Returns(new Mock<TableQuery<DynamicTableEntity>>().Object); // 直接返回预设数据 mockCloudTable.Setup(s => s.ExecuteQuery<DynamicTableEntity>(It.IsAny<TableQuery<DynamicTableEntity>>())) .Returns(testDynamicEntities.ToList());
关键说明
TableQuery本质是查询定义对象,而非数据集合,业务逻辑实际依赖的是ExecuteQuery返回的结果,因此MockExecuteQuery是核心。- 手动创建的
TableQuery因缺少IQueryable的Provider属性,调用Where等扩展方法时会触发空引用,需通过Mock补全必要属性。
内容的提问来源于stack exchange,提问作者Art
相关产品推荐
相关产品推荐

