如何在 xUnit 中使用条件逻辑模拟异步 LINQ 查询?

问题描述 投票:0回答:1

我正在为服务编写单元测试,我需要模拟更复杂的 LINQ 查询,其中包括条件逻辑和异步方法,例如

FirstOrDefaultAsync()

查询涉及多种链式方法,如过滤、排序和选择:

var result = await _products
    .Where(x => x.CategoryId == categoryId)
    .Where(x => x.Stock > 0)
    .OrderBy(x => x.Price)
    .FirstOrDefaultAsync();

我想模拟整个查询的行为,包括

Where()
OrderBy()
FirstOrDefaultAsync()
方法,并提供自定义结果以测试不同场景。

我正在使用 Moq 来模拟数据层,但我不确定如何模拟整个查询链并使其异步。

c# linq unit-testing moq xunit
1个回答
0
投票

正如 Jon Skeet 所建议的那样,Moq 不适合此类测试。我无法准确说出所提到的舞台中发生了什么(或者,我不知道如何查看该讨论),所以我会尝试详细说明。

Where
OrderBy
FirstOrDefaultAsync
这样的函数是扩展方法,因此不能被 Test Double 替换。 Stack Overflow 上有很多关于使用 Moq 测试扩展方法的问题,答案总是:你不能。这是一个:如何使用 Moq 来模拟扩展方法?

可能尝试做的是弄清楚如何在

Where
IEnumerable<T>
之上实现像
IQueryable<T>
这样的扩展方法,但这将使您的测试变得复杂且脆弱。测试将测试
Where
OrderBy
等的实现细节。此外,如果不访问每个扩展方法的源代码,这将很难做到,而且我不确定
FirstOrDefaultAsync
是否开放来源。

此外,如果您使用基于交互的测试,您将面临“脆弱测试”。该测试对预期交互的构成的看法过于狭隘。 考虑OP中的代码示例:

var result = await _products .Where(x => x.CategoryId == categoryId) .Where(x => x.Stock > 0) .OrderBy(x => x.Price) .FirstOrDefaultAsync();

即使您
假设

可以编写足够的起订量设置来模拟此交互,测试也会期望Where完全使用这两个谓词进行调用。

如果另一个程序员过来并决定将两个谓词融合在一起(这将是一个适当的优化),会发生什么?

var result = await _products .Where(x => x.CategoryId == categoryId && x.Stock > 0) .OrderBy(x => x.Price) .FirstOrDefaultAsync();

这是一个完全等效的查询,应该会产生完全相同的答案,但它会破坏基于交互的测试。

或者,假设

Stock

是一个整数,有人可能会决定将查询更改为:

var result = await _products
    .Where(x => x.CategoryId == categoryId)
    .Where(x => x.Stock >= 1)
    .OrderBy(x => x.Price)
    .FirstOrDefaultAsync();

再次强调,这一分钟的
重构

不应该改变系统的可观察行为,但它会破坏基于交互的测试。 最后,您可以将上述两个编辑结合起来:

var result = await _products .Where(x => x.CategoryId == categoryId && x.Stock >= 1) .OrderBy(x => x.Price) .FirstOrDefaultAsync();

同样,这不会改变查询的行为,但会破坏基于交互的测试。

查询越复杂,编写它的方式就越有可能不止一种,并且更容易发生变化。

因此,我建议您

支持基于状态的测试而不是基于交互的测试

。或者,正如 Jon Skeet 所写,使用 Fake Object 来代替。

© www.soinside.com 2019 - 2024. All rights reserved.