Макетирующий nHibernate QueryOver с Moq - PullRequest
10 голосов
/ 22 сентября 2011

Следующая строка завершается с нулевой ссылкой при тестировании:

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, так что я не очень уверен, что гуглить, чтобы получить нужную информацию.

Ответы [ 5 ]

4 голосов
/ 22 сентября 2011

Это не очень хорошая идея.Вы не должны издеваться над типами, которые вам не принадлежат .Вместо этого вы должны представить репозиторий , основывать его интерфейс на доменном / бизнес-языке и реализовывать его с помощью NHibernate.Реализация может использовать ICriteria, HQL, QueryOver, Linq и т. Д. Дело в том, что это решение будет инкапсулировано и скрыто от кода, который использует репозиторий.

Вы можете написать интеграционный тест , который проверит комбинацию вашего интерфейса + Real ORM + Real или поддельной базы данных.Пожалуйста, взгляните на это и это ответы о тестировании репозиториев и доступе к данным.Тестирование кода, использующего Repository, также очень просто, потому что вы можете имитировать интерфейс Repository.

В чем преимущества этого подхода, кроме тестируемости?Они в некоторой степени связаны друг с другом:

  • Разделение проблем.Проблемы с доступом к данным, решаемые на уровне доступа к данным (реализация репозитория).
  • Слабая связь.Остальная часть системы не связана с инструментом доступа к данным дня.У вас есть возможность переключить реализацию репозитория с NHibernate на raw sql, azure, веб-сервис.Даже если вам никогда не нужно переключаться, наложение будет лучше, если вы создадите «как будто», вам нужно переключиться.
  • Удобочитаемость.Объекты домена, включая определение интерфейса репозитория, основаны на языке бизнеса / домена.
3 голосов
/ 22 сентября 2011

Я использовал несколько подходов в прошлом.Один из способов, как другие предложили, создать класс репозитория, который вы можете смоделировать / заглушки, который инкапсулирует ваши запросы.Одна из проблем заключается в том, что она не очень гибкая, и в результате вы получаете хранимую процедуру, похожую на решение, за исключением того, что она в коде, а не в базе данных.

Последнее решение, которое я пробовал, - создание заглушки 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); }
    }
}
2 голосов
/ 22 сентября 2011

Не пытайтесь издеваться над QueryOver.Вместо этого определите интерфейс репозитория (который использует QueryOver для внутреннего использования) и смоделируйте этот интерфейс.

1 голос
/ 30 июля 2012

В последнее время я перемещаю код, который вызывает .QueryOver (), вместо этого в защищенный виртуальный метод, и строю свой собственный TestableXYZ, который наследуется от XYZ, переопределяет метод и возвращает пустой список или что-то еще. Таким образом, мне не нужен репозиторий только для тестирования.

1 голос
/ 22 сентября 2011

Я не думаю, что приведенный выше код является правильным.AFAIK QueryOver - это метод расширения в интерфейсе ISession, и вы не можете использовать метод Mock-расширений подобным образом (по крайней мере, с помощью обычных инструментов Mocking, таких как Moq или RhinoMocks).

...