试图模拟 IQueryable 实体框架查询
Posted
技术标签:
【中文标题】试图模拟 IQueryable 实体框架查询【英文标题】:Trying to Mock IQueryable Entity Framework Query 【发布时间】:2018-10-11 18:04:55 【问题描述】:我正在尝试测试查询是否不区分大小写。此生产代码有效:
public ILookup<string, EEntry> GetEEntries(int batchId, List<string> employeeIds)
using (WithNoLock())
var result = from e in _entities.EEntries
where e.CPayBatchProcessId == batchId
&& (!e.Blocked.HasValue || e.Blocked.Value != true)
&& employeeIds.Contains(e.Id)
select e;
return result.ToLookup(e => e.Id, StringComparer.OrdinalIgnoreCase);
我无法让单元测试工作。我的第一次尝试失败了,因为我相信列表是IEnumerable
而不是IQueryable
。但是,我在IQueryable
的尝试没有通过。该查询区分大小写,我不希望这样。这就是我所做的IQueryable
:
[TestCase("abc", "ABC")]
public void EEntriesAreCaseInsensitive(string employeeId1Input, string employeeId1Output)
var payEntries = new List<EEntry>
new EEntry() CPayBatchProcessId = 8, Id = employeeId1Input,
new EEntry() CPayBatchProcessId = 8, Id = "123"
.AsQueryable();
var payEntriesDbSet = new Mock<DbSet<EEntry>>();
payEntriesDbSet.As<IQueryable<EEntry>>().Setup(x => x.Provider).Returns(payEntries.Provider);
payEntriesDbSet.As<IQueryable<EEntry>>().Setup(x => x.Expression).Returns(payEntries.Expression);
payEntriesDbSet.As<IQueryable<EEntry>>().Setup(x => x.ElementType).Returns(payEntries.ElementType);
payEntriesDbSet.As<IQueryable<EEntry>>().Setup(x => x.GetEnumerator()).Returns(payEntries.GetEnumerator);
var context = new Mock<ISomeContext>();
context.Setup(x => x.EEntries).Returns(payEntriesDbSet.Object);
var employeeIds = new List<string>() "aBc", "dEf", "gHi" ;
var repo = new EEntriesRepository(context.Object);
var payEntryRecords = repo.GetEEntries(8, employeeIds);
Assert.IsTrue(payEntryRecords.Contains(employeeId1Output));
我错过了什么?
注意:EEntry.Id 的 getter 返回 .ToUpper()
。生产代码正确地忽略了这一点。测试代码没有。
【问题讨论】:
不管.Contains()
使用List
实现吗?哪个返回false。更改它以匹配您的生产代码:employeeIds.Contains(employeeId1Output, StringComparer.OrdinalIgnoreCase)
返回 true。
实际运行时,针对数据库,查询语法会转换为:([Extent1].[id] IN ('aBc', 'dEf', 'gHi'))
。这会针对不区分大小写的数据库运行。
我缺少的可能是查询语法不会仅仅因为它是 IQueryable 而被转换为 SQL。在单元测试期间,一切都在内存中。也许我在期待一些不可能的事情。
如果您返回 IEnumerable 并执行 var payEntryRecords = repo.GetEEntries(8, employeeIds).ToList();
会发生什么?那么忽略大小写的.Contains
重载是否有效?
我认为它刚刚击中了我。我正在尝试测试查询中是否区分大小写,但这并不是我真正应该测试的。这就像在单元测试中测试 SQL Server 功能一样。在单元测试中,我需要做的是确保查询返回数据(考虑区分大小写),然后测试 ILookup
在忽略大小写的情况下返回值。
【参考方案1】:
您似乎没有在任何地方使用employeeId1Output
,但您断言它应该在payEntryRecords
中。根据您的代码,您似乎应该断言 employeeId1Input
是否在 payEntryRecords
中。您明确地将 employeeId1Input
添加到您的 payEntries
对象中。
【讨论】:
employeeId1Output
是有意为输入的不同大小写。我想断言 ID 在那里,不管它的情况如何。如果我将第一个employeeId 更改为“ABC”(而不是“aBc”),测试就会通过。
嗯,这是.contains
方法的问题,而不是您的查询。 @mxmissile 关于过载是正确的。 This 问题也可能有帮助
我实际上尝试了该链接的建议,但 SQL Server 无法识别并失败。【参考方案2】:
字符串比较不区分大小写的性质取决于您的数据库提供商。 SQL Server 将比较字符串不区分大小写,而我相信 PostgreSQL 之类的则不会。
因此,如果您的代码逻辑执行employeeIds.Contains(e.Id)
,EF 会将等效逻辑传递给数据库。当用 Mock 和 List<T>.AsQueryable()
替换它时,C# 会将字符串视为区分大小写,而 SQL Server 不关心。 IN() 子句和字符串比较不区分大小写。
我会考虑使用以下或类似的方法来确保字符串比较不区分大小写。这应该适用于数据库提供程序,并且可以更好地处理模拟数据。
public ILookup<string, EEntry> GetEEntries(int batchId, List<string> employeeIds)
if(employeeIds == null) throw new ArgumentNullException("employeeIds");
employeeIds = employeeIds.ConvertAll(x => x.ToUpper());
using (WithNoLock())
var result = from e in _entities.EEntries
where e.CPayBatchProcessId == batchId
&& (!e.Blocked.HasValue || e.Blocked.Value != true)
&& employeeIds.Contains(e.Id.ToUpper())
select e;
return result.ToLookup(e => e.Id, StringComparer.OrdinalIgnoreCase);
【讨论】:
我喜欢这样,除了一件事:原始代码可以在 SQL Server 上正常工作。它只是不适用于单元测试。理想情况下,我不应该仅仅为了让单元测试工作而更改生产代码。我认为真正的答案是只测试ILookup
返回对象,这就是我最终要做的。以上是关于试图模拟 IQueryable 实体框架查询的主要内容,如果未能解决你的问题,请参考以下文章
实体框架,如何将 IQueryable 与多个 where 转换为 SQL 一起使用?