首页 > 解决方案 > 在木星 @BeforeAll 方法中得到 LazyInitializationException

问题描述

在我的集成测试中,我想使用@BeforeAll(见下文)预先在我的系统中填充一些我可以在我的测试用例中依赖的数据。

package com.ksteindl.chemstore.service;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.transaction.Transactional;
//some other import of my own stuff

@ExtendWith(SpringExtension.class)
@ActiveProfiles("test")
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class ShelfLifeServiceTest extends BaseControllerTest {

    @Autowired
    private ShelfLifeService shelfLifeService;
    @Autowired
    private ChemTypeService chemTypeService;

    @BeforeAll
    static void setUpTestDb(@Autowired ShelfLifeService shelfLifeService,@Autowired ChemTypeService chemTypeService) {
        ShelfLifeInput input = LabAdminTestUtils.getSolidForAlphaInput();
        Long chemTypeId = chemTypeService.getChemTypes().stream()
                .filter(chemType -> chemType.getName().equals(LabAdminTestUtils.SOLID_COMPOUND_NAME))
                .findAny()
                .get()
                .getId();
        input.setChemTypeId(chemTypeId);
        shelfLifeService.createShelfLife(input, AccountManagerTestUtils.BETA_LAB_MANAGER_PRINCIPAL);
    }

    @Test
    @Rollback
    @Transactional
    public void testCreateShelfLife_whenAllValid_gotNoException() {
        ShelfLifeInput input = LabAdminTestUtils.getSolidForBetaInput();
        Long chemTypeId = chemTypeService.getChemTypes().stream()
                .filter(chemType -> chemType.getName().equals(LabAdminTestUtils.SOLID_COMPOUND_NAME))
                .findAny()
                .get()
                .getId();
        input.setChemTypeId(chemTypeId);
        shelfLifeService.createShelfLife(input, AccountManagerTestUtils.BETA_LAB_MANAGER_PRINCIPAL);
    }
}

我的问题是当我运行测试时,我从 @BeforeAll 方法抛出了一个 LazyIncializtionException

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.ksteindl.chemstore.domain.entities.Lab.labManagers, could not initialize proxy - no Session
...
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.anyMatch(ReferencePipeline.java:528)
at com.ksteindl.chemstore.service.ShelfLifeService.getAndValidateLab(ShelfLifeService.java:92)
at com.ksteindl.chemstore.service.ShelfLifeService.createOrUpdateShelfLife(ShelfLifeService.java:65)
at com.ksteindl.chemstore.service.ShelfLifeService.createShelfLife(ShelfLifeService.java:47)
at com.ksteindl.chemstore.service.ShelfLifeServiceTest.setUpTestDb(ShelfLifeServiceTest.java:39)

有趣的是,当我注释掉 @BeforeAll 方法时,我的测试运行正常,没有任何异常。可以看到,两种方法的内容几乎是一样的(只是输入不同,防止相互冲突)。抛出异常的确切代码是这样的:

private Lab getAndValidateLab(String labKey, Principal principal) {
        Lab lab = labService.findLabByKey(labKey);
        lab.getLabManagers().stream().filter(/*some predicate*/).findAny().orElseThrow(/*throw validation Exception*/);
        return lab;
    }

没错,Lab 和 labManagers 属性之间的关系是惰性的,但我不明白为什么 Spring 不会在一个事务中获取这些数据,就像在testCreateShelfLife_whenAllValid_gotNoException. 另外,ShelfLifeService.createShelfLife()从 Rest Controller 调用中当然可以正常工作。谢谢你的帮助!实体:

@Entity
@Data
public class Lab {
    //...
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "MANAGER_OF_LAB_TABLE", joinColumns = @JoinColumn(name = "LAB_ID"), inverseJoinColumns = @JoinColumn(name = "APP_USER_ID"))
    @JsonIgnore
    private List<AppUser> labManagers;
}

@Entity
@Data
public class AppUser {
//...
    @ManyToMany(mappedBy = "labManagers")
    private List<Lab> managedLabs;
}

标签: springdatabasejpalazy-initializationjunit-jupiter

解决方案


将 @BeforeAll(在 Spring 容器初始化之前运行)更改为@BeforeEach(在每次测试之前,在 Spring 容器初始化之后运行),但在测试和 Spring 容器构建之后。

我认为 chemTypeService 是在 Spring 容器完全构建之前在 @BeforeAll 中执行的。- 一点猜测

还要查看@DirtiesContext以在每次测试之前重新构建 Spring 上下文。

看这个教程

另一个有点hacky的解决方案是在spring容器初始化后创建自己的类来加载测试数据:

@Service
@Profile("test")
public class TestInit {

    @Autowired
    private ShelfLifeService shelfLifeService;
    @Autowired
    private ChemTypeService chemTypeService;

    @PostConstruct
    private void init() {
        ShelfLifeInput input = LabAdminTestUtils.getSolidForAlphaInput();
        Long chemTypeId = chemTypeService.getChemTypes().stream()
                .filter(chemType -> chemType.getName().equals(LabAdminTestUtils.SOLID_COMPOUND_NAME))
                .findAny()
                .get()
                .getId();
        input.setChemTypeId(chemTypeId);
        shelfLifeService.createShelfLife(input, AccountManagerTestUtils.BETA_LAB_MANAGER_PRINCIPAL);
    }
}

推荐阅读