使用Moq模拟NHibernate的ISession

27

我正在使用NHibernate、ASP.NET MVC 2.0和StructureMap启动一个新项目,并使用NUnit和Moq进行测试。对于我的每个控制器,我都有一个单独的公共构造函数,其中注入了一个ISession。应用本身运行良好,但在单元测试方面,我基本上必须模拟一个ISession以便测试控制器。

当我尝试使用MOQ模拟ISession时,我会收到如下错误消息:

只支持中间调用中的属性访问

看起来我的问题是期望从框架CreateQuery方法获取用户列表,但在Google搜索该问题后,我现在更清楚了。

我的两个问题:

1)这是否是模拟ISession依赖注入的错误方法?

2)是否有一种方法可以修改代码以成功返回我的列表?

            [Test]
            public void DummyTest()
            {

                var mock = new Mock<ISession>();
                var loc = new Mock<User>();
                loc.SetupGet(x => x.ID).Returns(2);
                loc.SetupGet(x => x.FirstName).Returns("John");
                loc.SetupGet(x => x.LastName).Returns("Peterson");

                var lst = new List<User> {loc.Object};
                mock.Setup(framework => framework.CreateQuery("from User").List<User>()).Returns(lst);

                var controller = new UsersController(mock.Object);
                var result = controller.Index() as ViewResult;
               Assert.IsNotNull(result.ViewData);
            }
请注意,我很确定我可以创建一个硬编码的用户列表(而不是模拟单个用户并将其添加到列表中),但我决定将代码保留为现在的状态。
此外,该特定控制器的Index操作实质上执行了上面模拟的CreateQuery调用以返回数据库中的所有用户。这是一个人为制造的例子 - 不要从细节中读取任何东西。
提前感谢您的帮助。
编辑:回复下面的评论,我正在添加错误的堆栈跟踪。此外,User类上的所有属性都是虚拟的。

你能展示一下错误的堆栈跟踪吗?用户属性是抽象的还是虚拟的? - Blake Pettersson
2个回答

24

下面是我想出的解决方法,似乎完美地运行了。我没有测试 NHibernate,也没有测试数据库 - 我只是想测试依赖于 NHibernate 的控制器。最初解决方案的问题似乎是在 MOQ 设置调用中同时调用方法和读取会话的 List 成员。我通过将解决方案分成 QueryMock 和 Session Mock(create query 返回一个 IQuery 对象)来分解这些调用。事务模拟也是必要的,因为它是会话的依赖项(在我的情况下)...

        [Test]
        public void DummyTest()
        {
            var userList = new List<User>() { new User() { ID = 2, FirstName = "John", LastName = "Peterson" } };
            var sessionMock = new Mock<ISession>();
            var queryMock = new Mock<IQuery>();
            var transactionMock = new Mock<ITransaction>();

            sessionMock.SetupGet(x => x.Transaction).Returns(transactionMock.Object);
            sessionMock.Setup(session => session.CreateQuery("from User")).Returns(queryMock.Object);
            queryMock.Setup(x => x.List<User>()).Returns(userList);

            var controller = new UsersController(sessionMock.Object);
            var result = controller.Index() as ViewResult;
            Assert.IsNotNull(result.ViewData);
        }

19
与其嘲笑“会话(Session)”,我们可以考虑为单元测试设置一个不同的“配置(Configuration)”。这个单元测试“配置”使用快速、进程内的数据库,如SQLite或Firebird。在装置设置中,您完全从头创建一个新的测试数据库,运行脚本来设置表,并创建一组初始记录。在每个测试的设置中,您打开一个事务,并在测试后拆卸中回滚该事务,以将数据库恢复到其先前的状态。从某种意义上讲,您没有嘲弄“会话(Session)”,因为那变得棘手了,但您正在模拟实际的数据库。

谢谢Justice。我肯定已经考虑过了,如果我无法让这个工作起来,那就是我要走的路。但是在这个测试项目中,我正在尝试完全避免使用数据库。如果我可以模拟NHibernate,我觉得我将对我的测试有更多的控制力...但还是谢谢你的建议! - JP.
5
很不幸,当涉及到相关对象、延迟加载、缓存和其他所有NHibernate所做的事情时,NHibernate的“Session”非常复杂。因此,我建议跳过尝试模拟它,而是尝试模拟数据库。 NHibernate很容易从您的映射生成适用于任何给定数据库系统的架构创建脚本,然后在测试套件设置时执行该脚本以创建具有所需架构的空数据库。根据我的NHibernate经验以及观察Rails等框架的做法,这基本上是唯一可行的方法。 - yfeldblum
嗯,你说得对...虽然如果我们不需要使用一些模拟的数据库就好了,这将是非常好的事情...如果我们已经有了将我们的关系模型转换为对象模型的nhibernate,那么像这样进行测试就有点奇怪了,我们应该能够直接测试它...问题和答案都很好,干杯 :) - Marko
这一直是我首选的方法。在你的[SetUp]方法中使用SchemaExport等工具可以让像这样的事情变得轻而易举。 - Jarrett Meyer
7
除非你这样做时,你并没有运行单元测试,而是在运行一个带有数据库的集成测试。这时你失去了编写单元测试的任何好处。 - krillgar

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