views:

702

answers:

1

Dear Sirs,

The preface: I'm struggeling with LazyInitializationException in my Unit Tests, and I have a really hard time getting my head around it, as you can see from my questions Database Sessions in Spring, TestNG and Spring 3 and LazyInitializationException while unit-testing Hibernate entity classes for use in Spring, using TestNG

In order to be able to ask my question clearly, I've made a sample project on GitHub: http://github.com/niklassaers/Sample-Spring3-App/ In this sample project, I reproduce the problems I'm facing in my Spring3/Hibernate3/TestNG projects.

The question: I have two unit tests, they are quite alike, test the same class for the same collection of items using the same service. One runs, one fails. Why does the failing one fail? (or, why doesn't the running one fail in the same way?)

Here is the failing test:

@Test(timeOut=1000)
public void Roles() {
 User mockUser = userService.read(1);
 Assert.assertNotNull(mockUser);
 Assert.assertTrue(mockUser.isValid());
 Set<Role> roles = mockUser.getRoles();
 int size = roles.size();  // This line gives a LazyInitializationException
 Assert.assertTrue(size > 0);
}

full code at ( http://github.com/niklassaers/Sample-Spring3-App/blob/master/src/tld/mydomain/sample/entities/test/FailingUserUnitTest.java )

and here is the running test:

@Test
public void Roles() {
 for(int i = 1; i <= 4; i++) {
  User user = userService.read(i);
  Assert.assertNotNull(user);
  Assert.assertTrue(user.isValid());
  Set<Role> roles = user.getRoles();
  Assert.assertTrue(roles.size() > 0); // This line does not give a LazyInitializationException
  for(Role r : roles) {
   Assert.assertNotNull(r.getName());
   for(User someUser : r.getUsers())
    Assert.assertNotNull(someUser.getName());
  }
 }
}

full code at ( http://github.com/niklassaers/Sample-Spring3-App/blob/master/src/tld/mydomain/sample/entities/test/UserUnitTest.java )

Below follows the console output from running my tests. I understand that I have the services wrapped in a TransactionProxyFactoryBean (see http://github.com/niklassaers/Sample-Spring3-App/blob/master/WebRoot/WEB-INF/App-Model.xml), that makes them run in a transaction, and the unit tests are not wrapped, making the test like a view. The views I've "fixed" with OpenSessionInViewInterceptor. But I have learned that each unit test annotated with @Test in a class that extends from AbstractTransactionalTestNGSpringContextTests should also be wrapped in its own transaction, and indeed I've annotated both classes to rollback the transaction after each test is finished. That's why I'm doubly puzzled as to why one test fails and one does not. Any clues or solutions?

Feel free to modify the sample project at GitHub as you see fit, all the code should be there, but I've left out the jar-files for simplicity's sake. Here's the full output as promised:

[Parser] Running:
  /Users/niklas/Documents/Eclipse/SampleProject/testng.xml

2009-10-15 10:16:16,066 [TestNGInvoker-Roles()] ERROR org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: tld.mydomain.sample.entities.User.roles, no session or session was closed
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: tld.mydomain.sample.entities.User.roles, no session or session was closed
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
    at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119)
    at org.hibernate.collection.PersistentSet.size(PersistentSet.java:162)
    at tld.mydomain.sample.entities.test.FailingUserUnitTest.Roles(FailingUserUnitTest.java:33)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.testng.internal.MethodHelper.invokeMethod(MethodHelper.java:607)
    at org.testng.internal.InvokeMethodRunnable.runOne(InvokeMethodRunnable.java:49)
    at org.testng.internal.InvokeMethodRunnable.run(InvokeMethodRunnable.java:40)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:637)

===============================================
SampleAppSuite
Total tests run: 3, Failures: 1, Skips: 0
===============================================

Cheers

Nik

+1  A: 

Remove the timeOut=1000 from the @Test annotation. It appears that this is causing the test to be run in a separate thread (as is evident by the stacktrace, where the exception is thrown from a ThreadPool). Transactions and the SessionFactory are bound to the main thread, not to the test runner's thread, which is causing this exception.

I have run your example code and have gotten the test to work. In the future, it would be handy if you included a Maven2 pom.xml with your dependencies so that it is easier for those trying to compile your code to actually do so.

Ben L.
That's totally correct!! Thank you very, very much! :-) Is there any way that I can keep a time limit on it without, and thus letting it run as a separate thread, while keeping a hibernate transaction open for that thread? The point about the pom is taken. I've been running it all from Eclipse but I'll make sure to get Maven under my skin and include a pom in the project
niklassaers
You can make sure that the transaction is started inside of the Test method instead of relying on Spring to start the transaction for you. Get your transaction manager out of your spring context and do something like this:TransactionStatus tx = null;try { tx = txManager.getTransaction(new DefaultTransactionDefinition()); ... do test ...} finally { txManager.rollback(tx);}There is also probably something that you can do with the SessionFactory which will allow it to create a new session if one is not bound to the thread.P.S. Take a look at the M2Eclipse plugin for maven.
Ben L.
Thanks a lot, I'll check out M2Eclipse and I'll play around with doing the transaction management myself
niklassaers