0

I am new to NHibernate and even newer to MOQ (or other similar frameworks). After searching online day and night (google + stackoverflow + others), I am turning here for help.

The scenario is (should be) simple. I am trying to unit test a call on a C# WCF service that uses NHibernate as the ORM layer. The method, after doing some initial work, finds a database to connect to, and then calls on the SessionProvider (a manager of session factories) to return a nhibernate session for a sharded DB. I am then trying to use ISession.Get<>() to retrieve an object from the database aand then do some work. The problem is that the GUID (the key for the entry that I am looking up in the db) is generated at the begining of the call and I have no way of knowing what it might be beforehand outside the scope of the WCF call. Hence, I cannot use sqllite or other techniques to pre-populate the necessary data to control the test. What I was hoping for was that I can somehow mock (inject a fake layer to?) the call to Session.Get to return an invalid object which should cause the WCF call to throw.

Here's the test code snippet:

var testRequest = ... (request DTO)
var dummyBadObject = ... (entity in DB)

var mock = new Mock<ISession>(MockBehavior.Strict);
mock.Setup(m => m.Get<SampleObject>(It.IsAny<Guid>())).Returns(dummyBadObject);

var exception = Assert.Throws<FaultException>(() => applicationService.SomeMethod(testRequest));
Assert.AreEqual(exception.Code.ToString(), SystemErrorFault.Code.ToString());

When I run this test, instead of interacting with the mock ISession object, the app service code calls the Get on the actual ISession object from the session factory, connects to the database and gets the right object. Seems like I am missing something very basic about mocks or injection. Any help will be appreciated.

Thanks, Shawn

4

2 回答 2

1

根据我们的评论,问题在于模拟与您对它们的看法完全不同。

它们不会神奇地拦截从接口派生的类的创建。它们只是它的动态实现。

创建 aMock<ISession>与创建实现的类没有太大区别ISession。您仍然必须将其注入依赖它的服务中。

您可能必须审查整个堆栈,因为这样做的能力取决于良好的解耦设计。

推荐阅读:控制反转

于 2013-05-12T11:03:47.610 回答
0

我重新设计了应用程序中的组件,使其具有一个 ServiceContext 对象,该对象又包含应用程序使用的所有其他(过去是静态的)组件。在这种情况下,这将是会话提供程序(或 ISessionFactory 缓存),以及类似的 WCF 通道工厂缓存。不同之处在于 ServiceContext 提供了覆盖不同组件的默认实例的方法,允许我用模拟实例替换它们以进行测试并在测试完成时恢复原始实例。这使我能够创建一个测试,在其中我从会话缓存一直模拟到 ISession.Get/Save/Load 等。

var mockDatabaseSessionFactory = new Mock<DatabaseSessionManager>(MockBehavior.Strict);
var mockSession = new Mock<ISession>(MockBehavior.Strict);
var mockTransaction = new Mock<ITransaction>(MockBehavior.Strict);

mockDatabaseSessionFactory.Setup(x => x.GetIndividualMapDbSession()).Returns(mockSession.Object);
mockDatabaseSessionFactory.Setup(x => x.GetIndividualDbSession(It.IsAny<UInt32>())).Returns(mockSession.Object);
mockDatabaseSessionFactory.Setup(x => x.Dispose());
mockSession.Setup(x => x.BeginTransaction()).Returns(mockTransaction.Object);
mockSession.Setup(x => x.Dispose());
mockTransaction.Setup(x => x.Commit());
mockTransaction.Setup(x => x.Dispose());

// Setups to allow for the map insertion/deletion to pass
mockSession.Setup(x => x.Get<IndividualMap>(It.IsAny<string>())).Returns((IndividualMap)null);
mockSession.Setup(x => x.Load<IndividualMap>(It.IsAny<string>())).Returns((IndividualMap)null);
mockSession.Setup(x => x.Save(It.IsAny<IndividualMap>())).Returns(new object());
mockSession.Setup(x => x.Delete(It.IsAny<IndividualMap>()));

// Our test condition for this test: throw on attempt to save individual
mockSession.Setup(x => x.Save(It.IsAny<Individual>()))
    .Throws(new FaultException(ForcedTestFault.Reason, ForcedTestFault.Code));

// Test it - but be sure to back up the previous database session factory
var originalDbSessionFactory = ServiceContext.DatabaseSessionManager;
ServiceContext.OverrideDatabaseSessionManager(mockDatabaseSessionFactory.Object);
try
{
    var exception = Assert.Throws<FaultException>(() => applicationService.AddIndividual(addIndividualRequest));
    Assert.IsTrue(ForcedTestFault.Code.Name.Equals(exception.Code.Name));
}
catch (Exception)
{
    // Restore the original database session factory before rethrowing
    ServiceContext.OverrideDatabaseSessionManager(originalDbSessionFactory);
    throw;
}

ServiceContext.OverrideDatabaseSessionManager(originalDbSessionFactory);
ServiceContext.CommunicationManager.CloseChannel(applicationService);

幸运的是,代码设计并不算太糟糕 o_O :) 所以我很容易地重新考虑了这一点,现在代码覆盖率为 100!感谢 Diego 将我推向正确的方向。

于 2013-05-13T04:26:13.587 回答