这是错误的起订量设置还是起订量不足?
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)));
}
(我知道有不同的写法,这是我们最近尝试的迭代。)
ActivitiesMock
与 Get(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 true
或 false
正确:
Assert.IsTrue(actualPredicate.Compile().Invoke(validActivity));
G运行ted 现在它有点 hacky,但乍一看它似乎不太像垃圾箱火灾解决方案,这是我们确保提供的调用能做什么的一种方式我们期望他们这样做,这就是我们想要的。
更新(2016 年 9 月 7 日):到目前为止,这对我们来说效果很好。我们 运行 遇到了 LINQ-to-Entity 语句不按预期 运行 的问题,因为 LINQ 是区分大小写的,而生成的 SQL 不是,但自那以后对我们来说不是一个交易破坏者,我们很好。
我正在尝试测试以下代码:
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)));
}
(我知道有不同的写法,这是我们最近尝试的迭代。)
ActivitiesMock
与 Get(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 true
或 false
正确:
Assert.IsTrue(actualPredicate.Compile().Invoke(validActivity));
G运行ted 现在它有点 hacky,但乍一看它似乎不太像垃圾箱火灾解决方案,这是我们确保提供的调用能做什么的一种方式我们期望他们这样做,这就是我们想要的。
更新(2016 年 9 月 7 日):到目前为止,这对我们来说效果很好。我们 运行 遇到了 LINQ-to-Entity 语句不按预期 运行 的问题,因为 LINQ 是区分大小写的,而生成的 SQL 不是,但自那以后对我们来说不是一个交易破坏者,我们很好。