使用Moq模拟nHibernate QueryOver

11

在测试时,以下代码会出现空引用错误:

var awards = _session.QueryOver<Body>().Where(x => x.BusinessId == (int)business).List();

我的测试如下:

var mockQueryOver = new Mock<IQueryOver<Body, Body>>();
mockQueryOver.Setup(q => q.List()).Returns(new List<Body> {_awardingBody});
_mockSession.Setup(c => c.QueryOver<Body>()).Returns((mockQueryOver.Object));
_mockCommandRunner = new Mock<ICommandRunner>();
_generator = new CertificateGeneratorForOpenSSLCommandLine(_mockSession.Object, _mockCommandRunner.Object, _mockDirectory.Object, _mockFile.Object, _mockConfig.Object); 

说实话,我在这里摸索着,因为我对nHibernate和Moq相对较新,所以不太确定该搜索什么才能获得正确的信息。

6个回答

4
这不是一个好主意。你不应该模拟你没有拥有的类型。相反,你应该引入一个Repository,以业务领域语言为基础设计其接口,并使用NHibernate进行实现。实现可以使用ICriteria、HQL、QueryOver、Linq等。关键是这个决策将被封装并隐藏在使用Repository的代码中。
你可以编写一个集成测试,测试你的接口+真实ORM+真实或虚假数据库的组合。请查看关于测试存储库和数据访问的答案。测试使用Repository的代码也非常容易,因为你可以模拟Repository接口。
除了可测试性之外,这种方法有哪些优点?它们有一定的相关性:
- 关注点分离。数据访问问题在数据访问层(存储库实现)中得到解决。 - 松耦合。系统的其余部分不与当天的数据访问工具耦合。您可以从NHibernate切换存储库实现到原始SQL、Azure、Web服务。即使您永远不需要切换,如果您设计“好像”需要切换,分层就会更好地得到执行。 - 可读性。领域对象,包括存储库接口定义,基于业务/领域语言。

对于任何成熟的应用程序,数据访问策略都不是一时冲动决定的,当然也不会每天更改。将NHibernate(或EF)隐藏在存储库后面意味着您失去了NHibernate提供的好处(显式惰性/急切获取等...),除非您在存储库接口中声明每个可能的session.QueryOver / ICriteria排列。请参见The false myth of encapsulating data access in the DAL - Ayende @ Rahien - janv8000
@janv8000,你仍然可以使用nhibernate及其所有优点。我更喜欢将nhibernate视为实现细节,并专注于业务逻辑,确保我的领域模型使用与业务需求相同的语言。您不需要声明每个排列组合-您可以创建有意义的方法,例如“orders.Delinquent()”等。在罕见的情况下,您还可以使用规范模式,以便在查询属性的组合无法预测时(例如“高级搜索”功能)使用。 - Dmitry
关于Ayende的文章,我同意假设你可以轻松切换ORM是不现实的。但你仍然可以设计成“好像”你想要能够切换,这对设计的整体质量有巨大的好处(可读性、可测试性、松耦合)。是的,切换ORM很困难,大多数情况下也不现实,但这并不是创建一个紧密耦合且难以测试的代码的借口。 - Dmitry

3
我过去尝试了几种方法,其中一种方法是创建一个存储库类来封装您的查询,您可以对其进行模拟和存根。但是这种方法不太灵活,最终会像存储过程一样,只是这个过程在代码中而不是在数据库中。
最近我尝试的解决方案是创建一个QueryOver存根,在存根QueryOver方法时提供该存根。然后,我可以提供一个应返回的项目列表。请记住,如果您使用此方法,则不仅应编写单元测试,还应编写集成测试,以测试查询是否有效。
public class QueryOverStub<TRoot, TSub> : IQueryOver<TRoot, TSub>
{
    private readonly TRoot _singleOrDefault;
    private readonly IList<TRoot> _list;
    private readonly ICriteria _root = MockRepository.GenerateStub<ICriteria>();

    public QueryOverStub(IList<TRoot> list)
    {
        _list = list;
    }

    public QueryOverStub(TRoot singleOrDefault)
    {
        _singleOrDefault = singleOrDefault;
    }

    public ICriteria UnderlyingCriteria
    {
        get { return _root; }
    }

    public ICriteria RootCriteria
    {
        get { return _root; }
    }

    public IList<TRoot> List()
    {
        return _list;
    }

    public IList<U> List<U>()
    {
        throw new NotImplementedException();
    }

    public IQueryOver<TRoot, TRoot> ToRowCountQuery()
    {
        throw new NotImplementedException();
    }

    public IQueryOver<TRoot, TRoot> ToRowCountInt64Query()
    {
        throw new NotImplementedException();
    }

    public int RowCount()
    {
        return _list.Count;
    }

    public long RowCountInt64()
    {
        throw new NotImplementedException();
    }

    public TRoot SingleOrDefault()
    {
        return _singleOrDefault;
    }

    public U SingleOrDefault<U>()
    {
        throw new NotImplementedException();
    }

    public IEnumerable<TRoot> Future()
    {
        return _list;
    }

    public IEnumerable<U> Future<U>()
    {
        throw new NotImplementedException();
    }

    public IFutureValue<TRoot> FutureValue()
    {
        throw new NotImplementedException();
    }

    public IFutureValue<U> FutureValue<U>()
    {
        throw new NotImplementedException();
    }

    public IQueryOver<TRoot, TRoot> Clone()
    {
        throw new NotImplementedException();
    }

    public IQueryOver<TRoot> ClearOrders()
    {
        return this;
    }

    public IQueryOver<TRoot> Skip(int firstResult)
    {
        return this;
    }

    public IQueryOver<TRoot> Take(int maxResults)
    {
        return this;
    }

    public IQueryOver<TRoot> Cacheable()
    {
        return this;
    }

    public IQueryOver<TRoot> CacheMode(CacheMode cacheMode)
    {
        return this;
    }

    public IQueryOver<TRoot> CacheRegion(string cacheRegion)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> And(Expression<Func<TSub, bool>> expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> And(Expression<Func<bool>> expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> And(ICriterion expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> AndNot(Expression<Func<TSub, bool>> expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> AndNot(Expression<Func<bool>> expression)
    {
        return this;
    }

    public IQueryOverRestrictionBuilder<TRoot, TSub> AndRestrictionOn(Expression<Func<TSub, object>> expression)
    {
        throw new NotImplementedException();
    }

    public IQueryOverRestrictionBuilder<TRoot, TSub> AndRestrictionOn(Expression<Func<object>> expression)
    {
        throw new NotImplementedException();
    }

    public IQueryOver<TRoot, TSub> Where(Expression<Func<TSub, bool>> expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> Where(Expression<Func<bool>> expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> Where(ICriterion expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> WhereNot(Expression<Func<TSub, bool>> expression)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> WhereNot(Expression<Func<bool>> expression)
    {
        return this;
    }

    public IQueryOverRestrictionBuilder<TRoot, TSub> WhereRestrictionOn(Expression<Func<TSub, object>> expression)
    {
        return new IQueryOverRestrictionBuilder<TRoot, TSub>(this, "prop");
    }

    public IQueryOverRestrictionBuilder<TRoot, TSub> WhereRestrictionOn(Expression<Func<object>> expression)
    {
        return new IQueryOverRestrictionBuilder<TRoot, TSub>(this, "prop");
    }

    public IQueryOver<TRoot, TSub> Select(params Expression<Func<TRoot, object>>[] projections)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> Select(params IProjection[] projections)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> SelectList(Func<QueryOverProjectionBuilder<TRoot>, QueryOverProjectionBuilder<TRoot>> list)
    {
        return this;
    }

    public IQueryOverOrderBuilder<TRoot, TSub> OrderBy(Expression<Func<TSub, object>> path)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, path);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> OrderBy(Expression<Func<object>> path)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, path, false);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> OrderBy(IProjection projection)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, projection);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> OrderByAlias(Expression<Func<object>> path)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, path, true);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> ThenBy(Expression<Func<TSub, object>> path)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, path);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> ThenBy(Expression<Func<object>> path)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, path, false);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> ThenBy(IProjection projection)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, projection);
    }

    public IQueryOverOrderBuilder<TRoot, TSub> ThenByAlias(Expression<Func<object>> path)
    {
        return new IQueryOverOrderBuilder<TRoot, TSub>(this, path, true);
    }

    public IQueryOver<TRoot, TSub> TransformUsing(IResultTransformer resultTransformer)
    {
        return this;
    }

    public IQueryOverFetchBuilder<TRoot, TSub> Fetch(Expression<Func<TRoot, object>> path)
    {
        return new IQueryOverFetchBuilder<TRoot, TSub>(this, path);
    }

    public IQueryOverLockBuilder<TRoot, TSub> Lock()
    {
        throw new NotImplementedException();
    }

    public IQueryOverLockBuilder<TRoot, TSub> Lock(Expression<Func<object>> alias)
    {
        throw new NotImplementedException();
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, U>> path)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<U>> path)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, U>> path, Expression<Func<U>> alias)
    {
        return new QueryOverStub<TRoot, U>(_list);
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<U>> path, Expression<Func<U>> alias)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, U>> path, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<U>> path, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, U>> path, Expression<Func<U>> alias, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<U>> path, Expression<Func<U>> alias, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, IEnumerable<U>>> path)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<IEnumerable<U>>> path)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, IEnumerable<U>>> path, Expression<Func<U>> alias)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<IEnumerable<U>>> path, Expression<Func<U>> alias)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, IEnumerable<U>>> path, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<IEnumerable<U>>> path, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<TSub, IEnumerable<U>>> path, Expression<Func<U>> alias, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, U> JoinQueryOver<U>(Expression<Func<IEnumerable<U>>> path, Expression<Func<U>> alias, JoinType joinType)
    {
        return new QueryOverStub<TRoot, U>(new List<TRoot>());
    }

    public IQueryOver<TRoot, TSub> JoinAlias(Expression<Func<TSub, object>> path, Expression<Func<object>> alias)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> JoinAlias(Expression<Func<object>> path, Expression<Func<object>> alias)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> JoinAlias(Expression<Func<TSub, object>> path, Expression<Func<object>> alias, JoinType joinType)
    {
        return this;
    }

    public IQueryOver<TRoot, TSub> JoinAlias(Expression<Func<object>> path, Expression<Func<object>> alias, JoinType joinType)
    {
        return this;
    }

    public IQueryOverSubqueryBuilder<TRoot, TSub> WithSubquery
    {
        get { return new IQueryOverSubqueryBuilder<TRoot, TSub>(this); }
    }

    public IQueryOverJoinBuilder<TRoot, TSub> Inner
    {
        get { return new IQueryOverJoinBuilder<TRoot, TSub>(this, JoinType.InnerJoin); }
    }

    public IQueryOverJoinBuilder<TRoot, TSub> Left
    {
        get { return new IQueryOverJoinBuilder<TRoot, TSub>(this, JoinType.LeftOuterJoin); }
    }

    public IQueryOverJoinBuilder<TRoot, TSub> Right
    {
        get { return new IQueryOverJoinBuilder<TRoot, TSub>(this, JoinType.RightOuterJoin); }
    }

    public IQueryOverJoinBuilder<TRoot, TSub> Full
    {
        get { return new IQueryOverJoinBuilder<TRoot, TSub>(this, JoinType.FullJoin); }
    }
}

模拟和可测试的仓储封装了数据访问,就像存储过程一样?你更倾向于推荐将QueryOver分散到各个地方吗?这将破坏“领域层”并将其职责分散在UI和数据访问层之间。这肯定会非常灵活:http://en.wikipedia.org/wiki/Big_ball_of_mud - Dmitry
3
@Dimitry,几点建议。是的,将所有查询存储在一个仓库中就像拥有一组存储过程一样。如果他需要使用两个参数查询相同的实体,或者只想要一个投影呢?如果根据需要使用查询会“破坏领域层”吗?您的领域实体仍然包含业务逻辑。此外,NHibernate已经是数据访问的抽象。为什么您觉得需要进一步抽象它呢?这使得测试更容易,但在我看来,重构更困难。 - Vadim
直接使用NHibernate的方法免去了创建一堆只在一个地方使用的自定义存储库方法的麻烦。您仍然可以按照我在答案中概述的方法进行测试(单元测试的自定义存根,集成测试的完整数据库访问)。我仍然不明白不使用存储库进行数据查询意味着我们没有实践DDD。没有任何阻止您创建丰富的领域实体。您不能因为通常直接使用ActiveRecord查询数据而不能在RoR中实践DDD吗? - Vadim
ActiveRecord与领域模型是正交的,你要么使用AR,要么使用DM。当然,你可以在ActiveRecord之上实现领域模型,但这有什么意义呢?在我看来,你所推崇的方法等同于直接在代码库中散布SQL。唯一的优点是它提供了原始类型安全性。在我看来,它促进了紧密耦合,阻碍了单元测试,并且没有关注点分离。如果DDD代表数据驱动开发,那么它就是DDD。 - Dmitry
显示剩余4条评论

3
不要试图模拟 QueryOver。相反,定义一个存储库接口(其中内部使用 QueryOver),并模拟该接口。

2

我认为上面的代码不正确。据我所知,QueryOver是ISession接口的扩展方法,你不能像那样模拟扩展方法(至少不能用像Moq或RhinoMocks这样传统的Mocking工具)。


1
QueryOver 不是扩展方法。你可能想到的是 Query - Vadim

1
最近我已经将调用.QueryOver()的代码移动到一个受保护的虚拟方法中,并构建了自己的TestableXYZ,它继承自XYZ并覆盖该方法并返回一个空列表或其他内容。这样我就不需要为了测试而单独创建一个仓储库了。

0

NHibernate QueryOver异步代码可以使用c# moq进行模拟

var session = new Mock<NHibernate.ISession>();
session.Setup(x => x.QueryOver<Etype>().ListAsync(CancellationToken.None)).ReturnsAsync(data);
_dbContext.Setup(m => m.Session).Returns(session.Object);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接