这是错误的起订量设置还是起订量不足?

Is this a bad Moq setup or a deficiency in Moq?

我正在尝试测试以下代码:

public async Task<Activity> Get(long ID, Recruiter User, bool IsArchived = false)
{
    Activity result = await collection.FirstOrDefault(x => x.ID == ID && x.Recruiter.CompanyID == User.CompanyID && (!x.Archived || IsArchived));
    return result;
}

通过以下测试:

[TestMethod]
public async Task GetDoesThings()
{
    long ID = 1;
    bool IsArchived = false;
    Recruiter User = new Recruiter()
    {
        CompanyID = 1
    }; 

    ActivitiesMock.Setup(x => x.FirstOrDefault(y => y.ID == ID && y.Recruiter.CompanyID == User.CompanyID && (!y.Archived || IsArchived))).ReturnsAsync(new Activity());

    Activity result = await repo.Get(ID, User);

    ActivitiesMock.Verify(x => x.FirstOrDefault(y => y.ID == ID && y.Recruiter.CompanyID == User.CompanyID && (!y.Archived || IsArchived)));
}

(我知道有不同的写法,这是我们最近尝试的迭代。)

ActivitiesMockGet(long ID, Recruiter User, bool IsArchived = false) 中的 collection 相关。我们最近编写了包装器,以便更有效地尝试和测试我们的实体调用,但是在尝试验证调用是否正确时,我们 运行 遇到了这个错误:

Test method ExampleProject.Tests.Backend.Repositories.ActivityRepositoryTests.GetDoesThings threw exception: Moq.MockException: Expected invocation on the mock at least once, but was never performed: x => x.FirstOrDefault(y => (y.ID == .ID && y.Recruiter.CompanyID == .User.CompanyID) && (!(y.Archived) || .IsArchived))

Configured setups: x => x.FirstOrDefault(y => (y.ID == .ID && y.Recruiter.CompanyID == .User.CompanyID) && (!(y.Archived) || .IsArchived)), Times.Never

Performed invocations: IAppCollection`2.FirstOrDefault(x => (((x.ID == value(ExampleProject.Backend.Repositories.ActivityRepository+<>c__DisplayClass2_0).ID) AndAlso (x.Recruiter.CompanyID == value(ExampleProject.Backend.Repositories.ActivityRepository+<>c__DisplayClass2_0).User.CompanyID)) AndAlso (Not(x.Archived) OrElse value(ExampleProject.Backend.Repositories.ActivityRepository+<>c__DisplayClass2_0).IsArchived)))

在此实例中,包装器 (collection) 是接口的模拟。目标是确保存储库在包装器上调用正确的表达式,以便我们知道传递给实体 DbSet 的谓词是正确的,而不必担心所有混乱的异步抽象。

当测试为 运行 时,似乎没有找到具有完整谓词的模拟 Setup(),当我将 Setup() 更改为 It.IsAny<Expression<Func<Activity, bool>>>() 时,它运行模拟并提供 return,但 Verify 调用不起作用。所以,运行:

ActivitiesMock.Setup(x => x.FirstOrDefault(It.IsAny<Expression<Func<Activity, bool>>>())).ReturnsAsync(new Activity());

Activity result = await repo.Get(ID, User);

Assert.IsNotNull(result);
ActivitiesMock.Verify(x => x.FirstOrDefault(y => y.ID == ID && y.Recruiter.CompanyID == User.CompanyID && (!y.Archived || IsArchived)));

通过断言但未通过验证,而运行:

ActivitiesMock
    .Setup(x => x.FirstOrDefault(y => y.ID == ID && y.Recruiter.CompanyID == User.CompanyID && (!y.Archived || IsArchived)))
    .ReturnsAsync(new Activity())
    .Verifiable();

Activity result = await repo.Get(ID, User);

Assert.IsNotNull(result);
ActivitiesMock.Verify();

断言失败。

看起来它失败了,因为它需要相同的对象类型。我是在尝试做一些 Moq 无法处理的事情,还是我错过了一些我需要做的事情才能使验证正确?

根据要求,LINQ-to-Entity 包装器 (collection) 的具体实现是:

public Task<T> FirstOrDefault(Expression<Func<T, bool>> Predicate)
{
    return DbSet.FirstOrDefaultAsync(Predicate);
}

虽然包装器本身没有被使用,但是它的接口正在被模拟,我们正在测试的就是那个模拟。

答案都不是。由于匿名函数必须创建 class 个实例来存储所提供的数据,因此它们会创建 DisplayClass 个实例来保存数据。由于这些实例是在不同的命名空间中创建的(除其他外),当 Moq 对它们调用 .Equals 时,它们不会通过。

我们通过这样编写测试解决了这个问题:

ActivitiesMock
    .Setup(x => x.Where(It.IsAny<Expression<Func<Activity, bool>>>()))
    .Returns((Expression<Func<Activity, bool>> x) =>
    {
        actualPredicate = x;
        return queryMock.Object;
    });

然后创建有效和无效活动以提供给谓词以确保它 returns truefalse 正确:

Assert.IsTrue(actualPredicate.Compile().Invoke(validActivity));

G运行ted 现在它有点 hacky,但乍一看它似乎不太像垃圾箱火灾解决方案,这是我们确保提供的调用能做什么的一种方式我们期望他们这样做,这就是我们想要的。

更新(2016 年 9 月 7 日):到目前为止,这对我们来说效果很好。我们 运行 遇到了 LINQ-to-Entity 语句不按预期 运行 的问题,因为 LINQ 是区分大小写的,而生成的 SQL 不是,但自那以后对我们来说不是一个交易破坏者,我们很好。