In my integration tests I would like to populate my system with some data in advance that I can rely in my test cases, with @BeforeAll (see below).
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);
}
}
My problem is when I run tests, I got a LazyInicializtionException thrown from @BeforeAll method
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)
The funny thing is when I comment out the @BeforeAll method, my test runs as it should be without any Exception. As you can see, the content of the two methods are almost identical (only the input differs to prevent conflict each other). The exact code that is throwing the Exception is something like this:
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;
}
That is true, that the relation between Lab and labManagers property is Lazy, but I can't understand why Spring doesn't get those data in one transaction, just like in testCreateShelfLife_whenAllValid_gotNoException
. Plus of course ShelfLifeService.createShelfLife()
works perfectly fine from the Rest Controller call. Thanks you for your help!
The Entities:
@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;
}