如何通过 Moq 为单元测试中的模拟设置具体的 Expression<Func<TEntity, bool>>?

How to set up a concrete Expression<Func<TEntity, bool>> to a mock in a unit test through Moq?

我的存储库中有以下方法:

  Task<TEntity> FirstOrDefaultAsync(
        Expression<Func<TEntity, bool>> filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        CancellationToken cancellationToken = default);

我在服务中是这样调用的:

 var itemToUpdate = await _unitOfWork.ItemsRepository
                .FirstOrDefaultAsync(x => x.Id == itemDto.Id && x.UserId == userId);

问题是如何在单元测试中调用它?我对 ItemsRepository 有一个严格的模拟,当我这样调用它时,我得到一个异常,它是一个严格的模拟并且没有为我的场景设置:

            ItemsRepositoryMock
            .Setup(x => x.FirstOrDefaultAsync(x => x.Id == 1 && x.UserId == "ab70793b-cec8-4eba-99f3-cbad0b1649d0", null, CancellationToken.None))
            .ReturnsAsync(firstItem);

我应该如何使用我想要的参数设置模拟?

简短的回答是您不能直接在 Moq 中设置它。原因是就 c# 而言,这些表达式不相等。

你能做什么...

  1. 是编写您自己的表达式比较,根据该表达式中允许的内容,它可能更容易或更难。你用

    设置模拟
    It.Is<Expression<Func<TheEntity, bool>>(actual=>CompareExpressions(actual, x=>x => x.Id == 1 && x.UserId == "ab70793b-cec8-4eba-99f3-cbad0b1649d0")
    

并实施

CompareExpressions<TEntity>(Func<TEntity, bool> actual,Func<TEntity, bool> expected)

遍历表达式树并在检测到差异时执行 Assert.Fail 这并不难,但如果您之前没有这样做,这可能是一个挑战,特别是如果您希望允许等效和不相同的表达式.开始的好点是: ExpressionComparison

  1. 或者您可以编译表达式并使用匹配的元素调用它,您可以这样做

    It.Is<Expression<Func<TheEntity, bool>>(actual=>CompareExpressions(actual, new[]{ 
             new Sample<TheEntity>{ Subject= new TheEntity{ /*...*/ }, Expected = true, 
             new Sample /*...*/ )
    
    CompareExpressions(Func<TEntity, bool> actual, List<TEntity> samples) 
    {
        var method = actual.Compile(); 
        foreach(var sample in sampes)
           Assert.AreEqual(sample.Expected, method(sample.Subject));
    }
    

第二种方法更简单,但如果您准备的样本不够好,可能会出现误报。