1

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

1 Answers1

1

Change @BeforeAll (which runs before the Spring container is initialized) to @BeforeEach (which will be run before each test, after the Spring container is initialized), but after the test and Spring container has been built.

I think chemTypeService is being executed in @BeforeAll before the Spring container has been fully built. - BIT OF A GUESS

Look at @DirtiesContext also to re-build the Spring context before each test.

See this tutorial

Another, a bit hacky, solution is create your own class to load the test data after the spring container is initialised:

@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);
    }
}
Essex Boy
  • 7,565
  • 2
  • 21
  • 24
  • I've tried `@BeforeEach` and it is not throwing LazyInitializationException. You are probably right about `@BeforeAll` being runnung before Spring container is totally initialized. However I can use `@BeforeEach` only as a workaround, because I need this method to run only once before every test (which will to be couple hundred). – Kristóf Steindl Sep 16 '21 at 18:55
  • @KristófSteindl I would just accept the overhead of running the set-up before each test, but if that's really a problem I've added another idea to my answer. – Essex Boy Sep 17 '21 at 08:40