Search code examples
spring

Test Contexts affected by different test execution order


I'm NOT looking for a solution to this problem (it is @DirtiesContext), I want to understand WHY this happens depending on the execution order, or if it is a bug.

I have two classes that look exactly the same: one called SpringExtensionTest and the other ThisAnotherClassTest, both look like this:

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {H2JpaConfiguration.class})
class SpringExtensionTest {

    @Autowired
    private PersonRepository personRepository;

    @Test
    void name() {
        assertEquals(0, personRepository.count());
        Person testing = personRepository.save(new Person(UUID.randomUUID(), "first", "second"));
        assertNotNull(testing.getId());
    }

}

And I have another class that is called MySpecialTest that uses @SpringBootTest

@SpringBootTest
@ActiveProfiles("qa")
@TestPropertySource(properties = "logging.level.org.springframework.*=DEBUG")
public class MySpecialTest {

    @Autowired
    ApplicationContext applicationContext;

    @Autowired
    private PersonRepository personRepository;

    @Test
    void name() {
        assertEquals(0, personRepository.count());
        Person testing = personRepository.save(new Person(UUID.randomUUID(), "first", "second"));
        String[] activeProfiles = applicationContext.getEnvironment().getActiveProfiles();
        for (String activeProfile : activeProfiles) {
            System.out.println(activeProfile);
        }
    }
}

This is the config class

@TestConfiguration
@EnableJpaRepositories(basePackages = "com.example.spring_batch_demo")
public class H2JpaConfiguration {

    @Bean(destroyMethod = "shutdown")
    public EmbeddedDatabase dataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.example.spring_batch_demo");
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());

        Properties properties = new Properties();
        properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); 
        em.setJpaProperties(properties);

        return em;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }

}

If the order of execution is:

Scenario 1: SpringExtensionTest, ThisAnotherClassTest, MySpecialTest

  • Result: ThisAnotherClassTest fails, count = 1

Scenario 2: SpringExtensionTest, MySpecialTest, ThisAnotherClassTest

  • Result: success, looks like the DB was reset, but debugging org.springframework.test.context.cache.DefaultContextCache#put method, it's not creating a third context, there are only two created while running SpringExtensionTest and MySpecialTest

I want to understand how/why this is happening.

EDIT:
Scenario 3: same as Scenario2 but set a name to the database in H2JpaConfiguration

  • Result: ThisAnotherClassTest fails, count = 1.
@Bean(destroyMethod = "shutdown")
public EmbeddedDatabase dataSource() {
    return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .setName(UUID.randomUUID().toString()) // SET NAME TO DB
            .build();
}

Solution

  • I found the answer myself, but will write here so anyone can reference.
    EmbeddedDatabaseFactory has a default name if you don't specify one all the contexts will use the same database and not create new ones. But there is a tricky case that made this scenario 2 possible.

    Scenario 1:

    • When ThisAnotherClassTest is running it gets the dirty database from SpringExtensionTest, not yet cleaned by MySpecialTest. As expected.

    Scenario 2:

    • SpringExtensionTest - creates the H2 database with default name, adds to the database
    • MySpecialTest - tries to recreate the H2 database, as it uses the same name will get the one already created. In this scenario as I'm using @SpringBootTest this context will drop tables registered in JPA, we can see it in the logs org.hibernate.SQL: drop table if exists person cascade.
      • For @SpringBootTest context all the tests runs inside transactions, so even if saving a person to the database it is rolled back after each test.
    • ThisAnotherClassTest - will use the same Datasource of SpringExtensionTest, but as it was recently modified by MySpecialTest the test runs on an empty table and count = 0.

    Scenario 3:

    • As we are using an unique name for the SpringExtensionTest+ThisAnotherClassTest context, it won't be dropped by MySpecialTest (will create its own database with default name)