1

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

matt b
  • 138,234
  • 66
  • 282
  • 345
niklassaers
  • 8,480
  • 20
  • 99
  • 146

2 Answers2

6

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 Roy
  • 8,452
  • 10
  • 60
  • 84
  • 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 Oct 10 '09 at 10:52
  • 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 Roy Oct 10 '09 at 11:21
1

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
  • 99,456
  • 24
  • 206
  • 195
  • 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 Oct 10 '09 at 10:49
  • @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 Oct 10 '09 at 15:31
  • Thank you very much, I didn't catch that distinction :-) – niklassaers Oct 12 '09 at 06:19
  • Just adding to the correct answer, now you can also have an in-memory database, like h2, by just adding a dependency. This will save you from the headache of cleanup/tearDown. Concept: A in-memory database instance is created just for your test and is removed after your test is over. – Mohit Chawla Jun 13 '17 at 06:50