views:

359

answers:

2

Hi guys,

In my Spring configuration, I've asked that the session should remain open in my views:

  <bean name="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
    <property name="sessionFactory" ref="sessionFactory"/>
    <property name="flushMode" value="0" />
  </bean>

However, this bean obiously doesn't consider my TestNG unit tests as a view. ;-) That's all right, but is there a similar bean for unit tests so that I avoid the dreaded LazyInitializationException while unit-testing? So far, half of my unit tests die because of it.

My unit test typically looks like this:

@ContextConfiguration({"/applicationContext.xml", "/applicationContext-test.xml"})
public class EntityUnitTest extends AbstractTransactionalTestNGSpringContextTests {

  @BeforeClass
  protected void setUp() throws Exception {
    mockEntity = myEntityService.read(1);
  }

  /* tests */

  @Test
  public void LazyOneToManySet() {
    Set<SomeEntity> entities = mockEntity.getSomeEntitySet();
    Assert.assertTrue(entities.size() > 0); // This generates a LazyInitializationException
  }



}

I've tried changing the setUp() to this:

private SessionFactory sessionFactory = null;

@BeforeClass
protected void setUp() throws Exception {
  sessionFactory = (SessionFactory) this.applicationContext.getBean("sessionFactory");
  Session s = sessionFactory.openSession();
  TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(s));

  mockEntity = myEntityService.read(1);
}

but I believe this is the wrong way to go about it, and I mess the transaction up for later tests. Is there something like an OpenSessionInTestInterceptor, are there better ways of doing this, or is this the way to do it and in that case what have I misunderstood about it?

Cheers

Nik

+1  A: 

I uses JUnit for my tests, so you need to adapt the following example to TestNG. Personnaly I use SpringJUnit4ClassRunner to bind transactions in a base test class:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/applicationContext-struts.xml")
@TransactionConfiguration(transactionManager = "transactionManager")
@Transactional
public abstract class BaseTests {

And in "@Before", I inject a MockHttpServletRequest in the RequestContextHolder:

@Before
public void prepareTestInstance() throws Exception {
    applicationContext.getBeanFactory().registerScope("session", new SessionScope());
    applicationContext.getBeanFactory().registerScope("request", new RequestScope());
    MockHttpServletRequest request = new MockHttpServletRequest();

    ServletRequestAttributes attributes = new ServletRequestAttributes(request);
    RequestContextHolder.setRequestAttributes(attributes);

     .......

I took the information from the manual

Thierry-Dimitri Roy
Thanks, the manual refers much to the JUnit imlementation, but I've found things to be slightly different with the TestNG. Thanks for your prepareTestInstance(), that was very enlightening. Am I correct in understanding that your approach lays closer to my assumption of one transaction for one testclass?
niklassaers
No, it's one transaction per test (or method). In Junit @Before is called before each method. That way, any change to the DB is rollback after each test. Thus each time you run a test, the DB is in a known state.
Thierry-Dimitri Roy
Aha! Thanks a lot. :-)
niklassaers
+1  A: 

Umm.. not to be a smart-ass here, but that's not what setUp() was intended for.

The basic idea is to have your tests be self-sufficient and re-entrant meaning you should not depend on database having particular records nor should you permanently alter the database in your test. The process, thus, is to:

  1. Create any necessary records in setUp()
  2. Run your actual tests
  3. Clean up (if needed) in tearDown()

(1), each (2), and (3) all run in separate transactions - hence the problem you're getting with LazyInitializationException. Move mockEntity = myEntityService.read(1); from setUp into your actual test(s) and it will go away; use setUp if you need to create some test data, not as direct supplement to your individual test.

ChssPly76
Thank you very much for pointing that out, I was under the impression they would run in the same transaction unless transactions were manually rolled back/comitted and started. Them running in separate transactions explains a lot. :-) I guess I'll go back and re-read about AbstractTransactionalTestNGSpringContextTests. The part about self-sufficience, well, for ease of test I'd made a test-database with sample-data.
niklassaers
@niklassaers - you can annotate your setUp() with Spring's @Before causing it to run within the same transaction as your @Test annotated method: http://static.springsource.org/spring/docs/2.5.x/reference/testing.html#testcontext-tx I'm not sure how that jibes with @BeforeClass, though - you'll probably need to remove that as those two annotations define opposite behaviors (I believe the latter is specifying that method should run once per class, not once per test)
ChssPly76
Thank you very much, I didn't catch that distinction :-)
niklassaers