【问题标题】:LazyInitializationException while unit-testing Hibernate entity classes for use in Spring, using TestNG使用 TestNG 对 Spring 中使用的 Hibernate 实体类进行单元测试时出现 LazyInitializationException
【发布时间】:2009-10-09 12:37:55
【问题描述】:

在我的 Spring 配置中,我要求会话在我的视图中保持打开状态:

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

但是,这个 bean 显然没有将我的 TestNG 单元测试视为视图。 ;-) 没关系,但是是否有用于单元测试的类似 bean,以便我在单元测试时避免可怕的 LazyInitializationException?到目前为止,我有一半的单元测试因此而死。

我的单元测试通常如下所示:

@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
  }



}

我已尝试将 setUp() 更改为:

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);
}

但我认为这是错误的处理方式,我将交易搞砸以备后续测试。有没有像 OpenSessionInTestInterceptor 这样的东西,有没有更好的方法来做到这一点,或者这是这样做的方法,在这种情况下我误解了什么?

干杯

尼克

【问题讨论】:

    标签: java unit-testing hibernate spring testng


    【解决方案1】:

    我使用 JUnit 进行测试,因此您需要将以下示例改编为 TestNG。我个人使用 SpringJUnit4ClassRunner 在基础测试类中绑定事务:

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

    在“@Before”中,我在 RequestContextHolder 中注入了一个 MockHttpServletRequest:

    @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);
    
         .......
    

    我从manual获取信息

    【讨论】:

    • 谢谢,该手册在很大程度上参考了 JUnit 实施,但我发现 TestNG 的情况略有不同。感谢您的 prepareTestInstance(),这非常有启发性。我是否正确理解您的方法更接近我对一个测试类的一项事务的假设?
    • 不,这是每个测试(或方法)的一个事务。在 Junit 中,@Before 在每个方法之前被调用。这样,对数据库的任何更改都会在每次测试后回滚。因此,每次运行测试时,数据库都处于已知状态。
    【解决方案2】:

    嗯.. 不要在这里做个聪明人,但这不是setUp() 的目的。

    基本思想是让您的测试自给自足且可重入,这意味着您不应依赖具有特定记录的数据库,也不应在测试中永久更改数据库。因此,该过程是:

    1. setUp() 中创建任何必要的记录
    2. 运行实际测试
    3. 清理(如果需要)tearDown()

    (1)、每个 (2) 和 (3) 都在单独的事务中运行 - 因此您遇到 LazyInitializationException 的问题。将 mockEntity = myEntityService.read(1); 从 setUp 移动到您的实际测试中,它将消失;如果您需要创建一些测试数据,请使用 setUp,而不是作为您个人测试的直接补充。

    【讨论】:

    • 非常感谢您指出这一点,我的印象是除非手动回滚/提交并启动事务,否则它们会在同一个事务中运行。它们在单独的事务中运行解释了很多。 :-) 我想我会回去重新阅读有关 AbstractTransactionalTestNGSpringContextTests 的内容。关于自给自足的部分,好吧,为了便于测试,我用样本数据制作了一个测试数据库。
    • @niklassaers - 您可以使用 Spring 的 @Before 注释您的 setUp(),使其在与您的 @Test 注释方法相同的事务中运行:static.springsource.org/spring/docs/2.5.x/reference/… 我不确定这如何与 @不过,BeforeClass - 您可能需要删除它,因为这两个注释定义了相反的行为(我相信后者指定该方法应该每个类运行一次,而不是每个测试一次)
    • 非常感谢,我没注意到这个区别 :-)
    • 只需添加正确答案,现在您还可以通过添加依赖项来拥有内存数据库,如 h2。这将使您免于清理/拆卸的麻烦。概念:内存中的数据库实例仅为您的测试而创建,并在您的测试结束后被删除。
    猜你喜欢
    • 2011-11-17
    • 2015-12-25
    • 1970-01-01
    • 1970-01-01
    • 2018-01-11
    • 2011-07-21
    • 1970-01-01
    • 2012-10-19
    • 1970-01-01
    相关资源
    最近更新 更多