我正在尝试为新的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,我返回一个FinancialTransactions列表,所以人们会认为.Where()会根据它进行过滤。但是因为它是IQueryable,我猜它正在尝试将表达式全部作为一个来执行,当它枚举时。
为了验证,我将操作的查询更改为:
var model = DocumentSession.Query<FinancialTransaction>()
.ToList()
.Where(f => f.ResponsibleBusinessId == id);
这确实让测试通过,但是,它并不理想,因为这意味着它将枚举所有记录,然后过滤它们。
有没有办法让Moq使用它?
正如评论中所提到的,您不应该在测试中模拟RavenDB API。
由于InMemory模式,RavenDB对单元测试提供了出色的支持:
[Test]
public void MyTest()
{
using (var documentStore = new EmbeddableDocumentStore { RunInMemory = true })
{
documentStore.Initialize();
using (var session = documentStore.OpenSession())
{
// test
}
}
}
正如其他人所提到的,如果你可以使用内存/嵌入模式,那就很棒了 积分 测试。但在我看来,单元测试并不快或不够快。
我发现了一个 博客文章 由Sam Ritchie提供的“假”(包装在标准LINQ周围) IQueryable
)对于像这样的情况的IRavenQueryable。他有点过时,因为新版本的Raven(目前为2.5)提供了一些额外的方法 IRavenQueryable
接口。我目前不使用这些新方法(TransformWith
, AddQueryInput
, Spatial
),所以我懒得离开 NotImplementedException
在下面的代码中,暂时。
看到 山姆的帖子 对于我基于此的原始代码,以及用法示例。
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);
}
}