简体   繁体   English

单元测试 EF Core 3.1 异步方法

[英]Unit test EF Core 3.1 Async methods

I work on a project which doesn't have an integration tests setup.我在一个没有集成测试设置的项目上工作。

When I work on some backend task I need to have some test coverage for the stuff which is related to the interaction with the database.当我处理一些后端任务时,我需要对与数据库交互相关的内容进行一些测试。

I use EF Core 3.1, and because it already implements a repository pattern, I'm able to create extension methods per different entities.我使用 EF Core 3.1,因为它已经实现了存储库模式,所以我能够为每个不同的实体创建扩展方法。

And here the thing appears.事情就在这里出现了。 Each LINQ query is translated into pure SQL.每个 LINQ 查询都被翻译成纯 SQL。 This also means that the logic of the query must behave exactly the same as translated SQL code.这也意味着查询逻辑的行为必须与翻译后的 SQL 代码完全相同。

I've checked and I'm allowed to write unit tests against non-async methods however in the case of the Async methods it doesn't seem to be that trivial.我已经检查过并且我被允许针对非异步方法编写单元测试,但是在异步方法的情况下,它似乎并不是那么简单。

For example, I have the following extension method:例如,我有以下扩展方法:

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);
    }
}

This is unit tests coverage(written in xUnit):这是单元测试覆盖率(用 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);
}

When I run the test against the Async method I'm getting当我针对Async方法运行测试时,我得到

The provider for the source IQueryable doesn't implement IAsyncQueryProvider.源 IQueryable 的提供程序未实现 IAsyncQueryProvider。 Only providers that implement IAsyncQueryProvider can be used for Entity Framework asynchronous operations.只有实现 IAsyncQueryProvider 的提供程序才能用于实体框架异步操作。

I found this workaround however it only works for the EF core 2.2.我发现这个解决方法但是它只适用于 EF 核心 2.2。 I tried to somehow implement a similar idea for EF core 3.1 but without a result.我试图以某种方式为 EF 核心 3.1 实现类似的想法,但没有结果。 At the moment I covered the non-async method with tests however I use Async one in the production.目前我用测试覆盖了非异步方法,但是我在生产中使用了异步方法。 Better having something than nothing... Any ideas?有东西总比没有好...有什么想法吗? Cheers干杯

If you create an extension method called AnyAsyncOrSync() which checks whether the source provider is capable of asynchronous enumeration before attempting it...如果您创建一个名为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);
}

...you can catch the problem before it gets into the framework... ...您可以在问题进入框架之前发现问题...

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

Why not just force the method invocation to be synchronous by using .Result as shown here: http://bulletproofcoder.com/blog/testing-async-methods-with-xunit ?为什么不使用 .Result 强制方法调用同步,如下所示: http : //bulletproofcoder.com/blog/testing-async-methods-with-xunit

Edit: As the above link may not always be available.编辑:由于上述链接可能并不总是可用。 Two ways of unit testing asynchronous methods can usually be achieved by using .Result or GetAwaiter().GetResult() against the async method.单元测试异步方法的两种方法,通常可以通过使用来实现.ResultGetAwaiter().GetResult()针对异步方法。 After further research, it seems xUnit should be able to handle calling async operations simply by returning async Task in the test method.经过进一步研究,似乎 xUnit 应该能够通过在测试方法中返回async Task来处理调用异步操作。 See here: https://makolyte.com/csharp-how-to-unit-test-async-methods/请参阅此处: https : //makolyte.com/csharp-how-to-unit-test-async-methods/

Example:例子:

[TestMethod] public async Task SumTest_WhenInput1And2_Returns3()

It doesn't make sense to return IQueryable async.返回IQueryable异步是没有意义的。 It doesn't return data, just a query stub.它不返回数据,只是一个查询存根。

Return IEnumerableAsync or wrap it in a Task.Run( () => YourMethod())返回IEnumerableAsync或将其包装在Task.Run( () => YourMethod())

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM