模拟 IRavenQueryable,并附加一个 Where() 表达式

本文关键字:IRavenQueryable 一个 Where 表达式 模拟 | 更新日期: 2023-09-27 18:31:07

我正在尝试为新的 mvc3 项目做一些基本的概念验证类型代码。 我们正在将 Moq 与 RavenDB 一起使用。

行动:

public ActionResult Index(string id)
{
    var model = DocumentSession.Query<FinancialTransaction>()
        .Where(f => f.ResponsibleBusinessId == id);
    return View(model);
}

测试:

private readonly Fixture _fixture = new Fixture();
[Test]
public void Index_Action_Returns_List_Of_FinancialTransactions_For_Business([Random(0, 50, 5)]int numberOfTransactionsToCreate)
{
    // Arrange
    var session = new Mock<IDocumentSession>();
    var financialController = new FinancialController { DocumentSession = session.Object };
    var businessId = _fixture.CreateAnonymous<string>();
    var transactions = _fixture.Build<FinancialTransaction>()
        .With(f => f.ResponsibleBusinessId, businessId)
        .CreateMany(numberOfTransactionsToCreate);
    // Mock
    var ravenQueryableMock = new Mock<IRavenQueryable<FinancialTransaction>>();
    ravenQueryableMock.Setup(x => x.GetEnumerator()).Returns(transactions.GetEnumerator);
    ravenQueryableMock.Setup(x => x.Customize(It.IsAny<Action<Object>>()).GetEnumerator()).Returns(() => transactions.GetEnumerator());
    session.Setup(s => s.Query<FinancialTransaction>()).Returns(ravenQueryableMock.Object).Verifiable(); 
    // Act
    var actual = financialController.Index(businessId) as ViewResult;
    // Assert
    Assert.IsNotNull(actual);
    Assert.That(actual.Model, Is.InstanceOf<List<FinancialTransaction>>());
    var result = actual.Model as List<FinancialTransaction>;
    Assert.That(result.Count, Is.EqualTo(numberOfTransactionsToCreate));
    session.VerifyAll();
}

问题似乎出在 .Where(f => f.ResponsibleBusinessId == id). 从模拟的IRavenQueryable中,我返回了一个金融交易列表,所以人们会认为.Where() 将基于此进行过滤。 但是由于它是 IQueryable,我猜它在枚举时试图将表达式作为一个整体执行。

为了验证,我将操作的查询更改为以下内容:

var model = DocumentSession.Query<FinancialTransaction>()
    .ToList()
    .Where(f => f.ResponsibleBusinessId == id);

这确实可以让测试通过,但是,它并不理想,因为这意味着它将枚举所有记录,然后过滤它们。

有什么方法可以让最小起订量工作吗?

模拟 IRavenQueryable,并附加一个 Where() 表达式

正如评论中提到的,你不应该在测试中嘲笑RavenDB API。

RavenDB 对单元测试有很好的支持,这要归功于 InMemory 模式:

[Test]
public void MyTest()
{
    using (var documentStore = new EmbeddableDocumentStore { RunInMemory = true })
    {
        documentStore.Initialize();
        using (var session = documentStore.OpenSession())
        {
            // test
        }
    }
}

正如其他人所提到的,如果你能使用内存/嵌入式模式,它非常适合集成测试。但在我看来,对于单元测试来说,它不够快或不够容易。

我发现了Sam Ritchie的一篇博客文章,它为IRavenQueryable提供了一个"假的"(围绕标准LINQ IQueryable的包装器)。他的有点过时了,因为新版本的 Raven(目前为 2.5)在IRavenQueryable界面上提供了一些额外的方法。我目前不使用这些新方法(TransformWithAddQueryInputSpatial),所以我现在只是懒洋洋地在下面的代码中留下NotImplementedException

请参阅Sam的帖子,了解我基于此代码的原始代码以及使用示例。

public class FakeRavenQueryable<T> : IRavenQueryable<T> {
    private readonly IQueryable<T> source;
    public FakeRavenQueryable(IQueryable<T> source, RavenQueryStatistics stats = null) {
        this.source = source;
        this.QueryStatistics = stats;
    }
    public RavenQueryStatistics QueryStatistics { get; set; }
    public Type ElementType {
        get { return typeof(T); }
    }
    public Expression Expression {
        get { return this.source.Expression; }
    }
    public IQueryProvider Provider {
        get { return new FakeRavenQueryProvider(this.source, this.QueryStatistics); }
    }
    public IRavenQueryable<T> Customize(Action<IDocumentQueryCustomization> action) {
        return this;
    }
    public IRavenQueryable<TResult> TransformWith<TTransformer, TResult>() where TTransformer : AbstractTransformerCreationTask, new() {
        throw new NotImplementedException();
    }
    public IRavenQueryable<T> AddQueryInput(string name, RavenJToken value) {
        throw new NotImplementedException();
    }
    public IRavenQueryable<T> Spatial(Expression<Func<T, object>> path, Func<SpatialCriteriaFactory, SpatialCriteria> clause) {
        throw new NotImplementedException();
    }
    public IRavenQueryable<T> Statistics(out RavenQueryStatistics stats) {
        stats = this.QueryStatistics;
        return this;
    }
    public IEnumerator<T> GetEnumerator() {
        return this.source.GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator() {
        return this.source.GetEnumerator();
    }
}
public class FakeRavenQueryProvider : IQueryProvider {
    private readonly IQueryable source;
    private readonly RavenQueryStatistics stats;
    public FakeRavenQueryProvider(IQueryable source, RavenQueryStatistics stats = null) {
        this.source = source;
        this.stats = stats;
    }
    public IQueryable<TElement> CreateQuery<TElement>(Expression expression) {
        return new FakeRavenQueryable<TElement>(this.source.Provider.CreateQuery<TElement>(expression), this.stats);
    }
    public IQueryable CreateQuery(Expression expression) {
        var type = typeof(FakeRavenQueryable<>).MakeGenericType(expression.Type);
        return (IQueryable)Activator.CreateInstance(type, this.source.Provider.CreateQuery(expression), this.stats);
    }
    public TResult Execute<TResult>(Expression expression) {
        return this.source.Provider.Execute<TResult>(expression);
    }
    public object Execute(Expression expression) {
        return this.source.Provider.Execute(expression);
    }
}