使用 Moq 模拟 NHibernate ISession
Posted
技术标签:
【中文标题】使用 Moq 模拟 NHibernate ISession【英文标题】:Mocking an NHibernate ISession with Moq 【发布时间】:2009-12-01 20:57:48 【问题描述】:我正在使用 NHibernate、ASP.NET MVC 2.0 和 StructureMap 开始一个新项目,并使用 NUnit 和 Moq 进行测试。对于我的每个控制器,我都有一个公共构造函数,一个 ISession 被注入其中。应用程序本身工作得很好,但就单元测试而言,我基本上必须模拟一个 ISession 才能测试控制器。
当我尝试使用 MOQ 模拟 ISession 时,我收到以下错误消息:
仅支持属性访问 在中间调用中
看来我的问题是期待来自框架 CreateQuery 方法的用户列表,但在谷歌搜索问题后,我现在更清楚了。
我有两个问题:
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 类的所有属性都是虚拟的。
测试用例 'Beta.Tests.Unit.Controllers.UserControllerTest.Details_InValidIndex_ReturnsNotFoundView' 失败:System.NotSupportedException: 仅支持属性访问 在对 a 的中间调用中 设置。不支持的表达式 framework.CreateQuery("来自用户")。 在 Moq.Mock.AutoMockPropertiesVisitor.VisitMethodCall(MethodCallExpression m) 在 Moq.ExpressionVisitor.Visit(表达 exp)在 Moq.Mock.AutoMockPropertiesVisitor.VisitMethodCall(MethodCallExpression m) 在 Moq.ExpressionVisitor.Visit(表达 exp)在 Moq.Mock.AutoMockPropertiesVisitor.SetupMocks(表达式 表达式)在 Moq.Mock.GetInterceptor(LambdaExpression lambda,模拟模拟)在 Moq.Mock.c__DisplayClass12
2.<Setup>b__11() at Moq.PexProtector.Invoke[T](Func
1 功能)在 Moq.Mock.Setup[T1,TResult](模拟模拟, 表达式1 expression) at Moq.Mock
1.Setup[TResult](表达式`1 表达) 控制器\UserControllerTest.cs(29,0): 在 Beta.Tests.Unit.Controllers.UserControllerTest.Details_InValidIndex_ReturnsNotFoundView()
【问题讨论】:
你能显示错误的堆栈跟踪吗?用户属性是抽象的还是虚拟的? 【参考方案1】:以下是我想出的解决方案,似乎效果很好。同样,我没有测试 NHibernate,也没有测试数据库——我只是想测试依赖于 NHibernate 的控制器。初始解决方案的问题似乎是我正在调用方法以及在 MOQ 设置调用中读取会话的 List 成员。我通过将解决方案分解为 QueryMock 和 Session Mock(创建查询返回 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);
【讨论】:
【参考方案2】:与其模拟Session
,不如考虑为单元测试设置一个不同的Configuration
。这个单元测试Configuration
使用一个快速的进程内数据库,如 SQLite 或 Firebird。在夹具设置中,您完全从头开始创建一个新的测试数据库,运行脚本来设置表,并创建一组初始记录。在每次测试设置中,您打开一个事务,在测试后拆解中,您回滚事务以将数据库恢复到之前的状态。从某种意义上说,您不是在模拟 Session
,因为这很棘手,而是在模拟实际的数据库。
【讨论】:
感谢正义。我肯定想到了这一点,如果我不能让这个工作,这将是我要走的路。但我试图在这个测试项目中完全避免使用数据库。如果我可以模拟 NHibernate,我觉得我将对我的测试有更多的控制权......但是感谢您的建议! 不幸的是,NHibernateSession
在涉及到相关对象、延迟加载、缓存以及 NHibernate 所做的所有其他事情时非常复杂。所以我会跳过尝试模拟它,而是尝试模拟数据库。 NHibernate 很容易从您的映射中为任何给定的数据库系统生成模式创建脚本,然后您可以执行该脚本以在夹具设置上使用您的模式创建一个空数据库。根据我自己使用 NHibernate 的经验以及观察像 Rails 这样的框架所做的事情,这基本上是唯一的方法。
嗯,你是对的......虽然它会很好,我们不需要使用一些模拟数据库......如果我们已经有了将我们的关系模型转换为对象模型的 nhibernate,这样测试有点奇怪,我们应该可以直接测试它......很好的问题和答案,干杯:)
这是我的首选方法。在你的 [SetUp]
方法中使用像 SchemaExport
这样的东西只会让这样的事情变得太容易了。
除非您这样做,您不是在运行单元测试,而是在运行与数据库的集成测试。然后,您就失去了编写单元测试的任何好处。以上是关于使用 Moq 模拟 NHibernate ISession的主要内容,如果未能解决你的问题,请参考以下文章