这是一个糟糕的Moq设置还是Moq的缺陷?

时间:2016-06-24 14:03:58

标签: c# 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)));
}

(我知道有不同的写作方式,这是我们尝试过的最新迭代。)

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

  

测试方法ExampleProject.Tests.Backend.Repositories.ActivityRepositoryTests.GetDoesThings抛出异常:   Moq.MockException:   模拟上的预期调用至少一次,但从未执行过:x =&gt; x.FirstOrDefault(y =&gt;(y.ID == .ID&amp;&amp; y.Recruiter.CompanyID == .User.CompanyID)&amp;&amp;(!(y.Archived)|| .IsArchived))< / p>      

已配置的设置:   x =&gt; x.FirstOrDefault(y =&gt;(y.ID == .ID&amp;&amp; y.Recruiter.CompanyID == .User.CompanyID)&amp;&amp;(!(y.Archived)|| .IsArchived)), Times.Never

     

执行调用:   IAppCollection`2.FirstOrDefault(x =&gt;(((x.ID == value(ExampleProject.Backend.Repositories.ActivityRepository +&lt;&gt; c__DisplayClass2_0).ID)AndAlso(x.Recruiter.CompanyID == value(ExampleProject。 Backend.Repositories.ActivityRepository +&lt;&gt; c__DisplayClass2_0).User.CompanyID))AndAlso(Not(x.Archived)OrElse值(ExampleProject.Backend.Repositories.ActivityRepository +&lt;&gt; c__DisplayClass2_0).IsArchived)))

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

运行测试时似乎找不到包含完整谓词的模拟Setup(),当我将Setup()更改为It.IsAny<Expression<Func<Activity, bool>>>()时,它会运行模拟并提供返回,但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);
}

虽然没有使用包装器本身,但是它的界面被嘲笑,而且我们正在测试它的模拟。

1 个答案:

答案 0 :(得分:2)

答案都不是。由于匿名函数必须创建类实例来存储所提供的数据,因此它们会创建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;
    });

然后创建有效和无效的活动以提供给谓词,以确保它正确返回truefalse

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

现在说它有点黑客,但乍一看它看起来不像垃圾箱解决方案,而且它是我们确保所提供的电话做什么的一种方式我们期待他们,这就是我们想要的。

更新(2016年9月7日):到目前为止,这对我们来说一直很好。我们遇到过LINQ-to-Entity语句没有按预期运行的问题,因为LINQ区分大小写并且生成的SQL不是,但是因为那不是交易对我们而言,我们很好。