【问题标题】:Unit test EF Core 3.1 Async methods单元测试 EF Core 3.1 异步方法
【发布时间】:2021-09-19 08:58:24
【问题描述】:

我在一个没有集成测试设置的项目上工作。

当我处理一些后端任务时,我需要对与数据库交互相关的内容进行一些测试覆盖。

我使用 EF Core 3.1,因为它已经实现了存储库模式,所以我能够为不同的实体创建扩展方法。

然后事情就出现在这里。每个 LINQ 查询都被转换为纯 SQL。这也意味着查询的逻辑必须与翻译后的 SQL 代码完全相同。

我已经检查过了,我被允许针对非异步方法编写单元测试,但是在异步方法的情况下,它似乎并不那么简单。

例如我有如下扩展方法:

public static class PostcodeExclusionExtension
{

    public static bool CheckPostcodeIsSupported(this IQueryable<PostcodeExclusion> postcodeExclusion, int customerId, string postcode) =>
        !postcodeExclusion.Any(ApplyIsSupportedExpression(customerId, postcode));

    public static async Task<bool> CheckPostcodeIsSupportedAsync(this IQueryable<PostcodeExclusion> postcodeExclusion, int customerId, string postcode) =>
        !await postcodeExclusion.AnyAsync(ApplyIsSupportedExpression(customerId, postcode));

    private static Expression<Func<PostcodeExclusion, bool>> ApplyIsSupportedExpression(int customerId, string postcode)
    {
        postcode = postcode.Replace(" ", "").ToUpper();
        postcode = postcode.Insert(postcode.Length - 3, " ");
        var postcodeMatches = Enumerable.Range(0, postcode.Length)
                                    .Select(x => postcode.Substring(0, x + 1))
                                    .ToArray();

        return x => x.CustomerID == customerId && postcodeMatches.Contains(x.Postcode);
    }
}

这是单元测试覆盖率(用 xUnit 编写):

public static int validCustomerId = 1;
public const string validPostcode = "SW1A0AA";

public static IEnumerable<object[]> CheckPostcodeExclusion_should_validate_postcode_Inputs = new List<object[]>
{
    new object[] { true, validPostcode, validCustomerId, new List<PostcodeExclusion> { new PostcodeExclusion() { CustomerID = validCustomerId, Postcode = "A" } } },
    new object[] { true, validPostcode, validCustomerId, new List<PostcodeExclusion> { new PostcodeExclusion() { CustomerID = validCustomerId, Postcode = "SX" } } },
    //other test cases...    
};

[Theory]
[MemberData(nameof(CheckPostcodeExclusion_should_validate_postcode_Inputs))]
public async Task CheckPostcodeExclusion_should_validate_postcode(bool expectedPostcodeIsSupported, string postcode, int customerId, IEnumerable<PostcodeExclusion> postcodeExclusionSet)
{
    var isSupported = await postcodeExclusionSet.AsQueryable().CheckPostcodeIsSupportedAsync(customerId, postcode);
    Assert.Equal(expectedPostcodeIsSupported, isSupported);
}

当我针对 Async 方法运行测试时,我得到了

源 IQueryable 的提供程序未实现 IAsyncQueryProvider。只有实现 IAsyncQueryProvider 的提供程序才能用于实体框架异步操作。

我找到了this workaround,但它仅适用于 EF 核心 2.2。我试图以某种方式为 EF core 3.1 实现类似的想法,但没有结果。目前我用测试覆盖了非异步方法,但是我在生产中使用了异步方法。有总比没有好... 有任何想法吗?干杯

【问题讨论】:

  • 您能否详细说明一下:我试图以某种方式为 EF core 3.1 实现类似的想法,但没有结果。 ?
  • @PeterCsala 是的,所以我尝试修改this answer 中提供的代码以使其适用于 EF Core 3.1,但没有积极的结果..
  • 您尝试过内存提供程序吗?如果您的测试不依赖于导航属性,它会非常方便。 docs.microsoft.com/en-us/ef/core/testing/in-memory
  • @GoldenAge Gary McGill 在 cmets 部分中为 3.x 留下了 link。你也检查了吗?
  • @GoldenAge 什么,具体来说,那个解决方案没有用?只是说它不起作用是没有帮助的。提供有关所发生情况的详细信息,以及这与您预期发生的情况有何不同。 =

标签: c# ef-core-3.1


【解决方案1】:

如果您创建一个名为 AnyAsyncOrSync() 的扩展方法,该方法会在尝试之前检查源提供者是否能够进行异步枚举...

public static async Task<bool> AnyAsyncOrSync<TSource>([NotNull] this IQueryable<TSource> source, [NotNull] Expression<Func<TSource, bool>> predicate, CancellationToken cancellationToken = default)
{
    return source is IAsyncEnumerable<TSource>
        ? await source.AnyAsync(predicate, cancellationToken)
        : source.Any(predicate);
}

...您可以在问题进入框架之前发现它...

!await postcodeExclusion.AnyAsyncOrSync(ApplyIsSupportedExpression(customerId, postcode));

【讨论】:

    【解决方案2】:

    异步返回IQueryable 没有意义。它不返回数据,只是一个查询存根。

    返回IEnumerableAsync 或将其包装在Task.Run( () =&gt; YourMethod())

    【讨论】:

      猜你喜欢
      • 2020-07-14
      • 1970-01-01
      • 2011-11-07
      • 2015-02-13
      • 2023-03-06
      • 1970-01-01
      • 1970-01-01
      • 2017-12-21
      • 2020-05-14
      相关资源
      最近更新 更多