0

The issue I am seeing, is that @Transactional is causing a transaction to be created and rolled back, however the data is being carried over from one test to the next.

Versions in use:

implementation('org.springframework.boot:spring-boot-starter-data-jpa')
testCompile "org.testcontainers:testcontainers:1.12.1"
testCompile "org.testcontainers:mysql:1.12.1"

Code:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@ContextConfiguration(initializers = {Java8RepoTests.Initializer.class})
@Slf4j
@Transactional 
public class Java8RepoTests {
    @ClassRule
    public static MySQLContainer mysql = new MySQLContainer();

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        logger.info("setUpBeforeClass()");
        mysql.start();
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception {
        logger.info("tearDownAfterClass()");
        mysql.stop();
    }

    @Autowired
    private EntityManagerFactory entityManagerFactory;

    private EntityManager em;

    @Before
    public void setUp() throws Exception {
        logger.info("setUp()");
        em = entityManagerFactory.createEntityManager();
    }

    @After
    public void tearDown() throws Exception {
        logger.info("tearDown()");
        em.close();
    }

    @Autowired UserRepository repository;

    @Test
    public void testOne() {
        int count = Iterables.size(repository.findAll());
        // count = 0 at this point

        User user = new User();
        user = FillWithTestData(user);
        User targetUser = repository.save(user);

        count = Iterables.size(repository.findAll());   
        // count = 1
    }

    @Test
    public void testTwo() {         
        int count = Iterables.size(repository.findAll());
        // count = 1 at this point, should be zero!

        User user = new User();
        User targetUser= repository.save(user);
        // Exception thrown that user already exists
    }

    static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            TestPropertyValues.of(
                    "spring.datasource.url=" + mysql.getJdbcUrl(),
                    "spring.datasource.username=" + mysql.getUsername(),
                    "spring.datasource.password=" + mysql.getPassword()
                    ).applyTo(configurableApplicationContext.getEnvironment());

        }
    }
}

Log output:

2019-09-16 13:55:01.074  INFO 21456 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2019-09-16 13:55:07.936  INFO 21456 --- [           main] o.s.s.c.ThreadPoolTaskExecutor           : Initializing ExecutorService 'applicationTaskExecutor'
2019-09-16 13:55:08.372  WARN 21456 --- [           main] aWebConfiguration$JpaWebMvcConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2019-09-16 13:55:13.312  INFO 21456 --- [           main] o.s.t.c.t.TransactionContext             : Began transaction (1) for test context [DefaultTestContext@7a22302c testClass = Java8RepoTests, testInstance = com.test.api.Java8RepoTests@4af50246, testMethod = testOne@Java8RepoTests, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@45482f82 testClass = Java8RepoTests, locations = '{}', classes = '{class com.test.api.ApiApplication}', contextInitializerClasses = '[class com.test.api.Java8RepoTests$Initializer]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5af3afd9, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5ffead27, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@1fa268de, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@222545dc], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@448fa659]; rollback [true]
Hibernate: select user0_.id as id1_11_, user0_.created_date as created_2_11_, user0_.updated_date as updated_3_11_, user0_.created_by as created_4_11_, user0_.last_modified_by as last_mod5_11_, user0_.active as active6_11_, user0_.email as email7_11_, user0_.enabled as enabled8_11_, user0_.first_name as first_na9_11_, user0_.is_using2fa as is_usin10_11_, user0_.last_name as last_na11_11_, user0_.password as passwor12_11_ from users user0_
Hibernate: insert into users (created_date, updated_date, created_by, last_modified_by, active, email, enabled, first_name, is_using2fa, last_name, password) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: select user0_.id as id1_11_, user0_.created_date as created_2_11_, user0_.updated_date as updated_3_11_, user0_.created_by as created_4_11_, user0_.last_modified_by as last_mod5_11_, user0_.active as active6_11_, user0_.email as email7_11_, user0_.enabled as enabled8_11_, user0_.first_name as first_na9_11_, user0_.is_using2fa as is_usin10_11_, user0_.last_name as last_na11_11_, user0_.password as passwor12_11_ from users user0_
Hibernate: select user0_.id as id1_11_0_, user0_.created_date as created_2_11_0_, user0_.updated_date as updated_3_11_0_, user0_.created_by as created_4_11_0_, user0_.last_modified_by as last_mod5_11_0_, user0_.active as active6_11_0_, user0_.email as email7_11_0_, user0_.enabled as enabled8_11_0_, user0_.first_name as first_na9_11_0_, user0_.is_using2fa as is_usin10_11_0_, user0_.last_name as last_na11_11_0_, user0_.password as passwor12_11_0_, credential1_.user_id as user_id1_9_1_, credential2_.id as credenti2_9_1_, credential2_.id as id1_2_2_, credential2_.created_date as created_2_2_2_, credential2_.updated_date as updated_3_2_2_, credential2_.created_by as created_4_2_2_, credential2_.last_modified_by as last_mod5_2_2_, credential2_.cloud_provider_id as cloud_pr8_2_2_, credential2_.access_key as access_k6_2_2_, credential2_.owner_id as owner_id9_2_2_, credential2_.secret_key as secret_k7_2_2_, credential2_.virtual_datacenter_id as virtual10_2_2_, cloudprovi3_.id as id1_1_3_, cloudprovi3_.created_date as created_2_1_3_, cloudprovi3_.updated_date as updated_3_1_3_, cloudprovi3_.name as name4_1_3_, user4_.id as id1_11_4_, user4_.created_date as created_2_11_4_, user4_.updated_date as updated_3_11_4_, user4_.created_by as created_4_11_4_, user4_.last_modified_by as last_mod5_11_4_, user4_.active as active6_11_4_, user4_.email as email7_11_4_, user4_.enabled as enabled8_11_4_, user4_.first_name as first_na9_11_4_, user4_.is_using2fa as is_usin10_11_4_, user4_.last_name as last_na11_11_4_, user4_.password as passwor12_11_4_, virtualdat5_.id as id1_13_5_, virtualdat5_.created_date as created_2_13_5_, virtualdat5_.updated_date as updated_3_13_5_, virtualdat5_.created_by as created_4_13_5_, virtualdat5_.last_modified_by as last_mod5_13_5_, virtualdat5_.blueprint_id as blueprin7_13_5_, virtualdat5_.name as name6_13_5_, virtualdat5_.organization_id as organiza8_13_5_, virtualdat5_.owner_id as owner_id9_13_5_, roles6_.user_id as user_id1_10_6_, role7_.id as role_id2_10_6_, role7_.id as id1_7_7_, role7_.created_date as created_2_7_7_, role7_.updated_date as updated_3_7_7_, role7_.title as title4_7_7_ from users user0_ left outer join user_credentials credential1_ on user0_.id=credential1_.user_id left outer join credentials credential2_ on credential1_.credential_id=credential2_.id left outer join cloud_providers cloudprovi3_ on credential2_.cloud_provider_id=cloudprovi3_.id left outer join users user4_ on credential2_.owner_id=user4_.id left outer join virtual_datacenters virtualdat5_ on credential2_.virtual_datacenter_id=virtualdat5_.id left outer join user_role roles6_ on user0_.id=roles6_.user_id left outer join role role7_ on roles6_.role_id=role7_.id where user0_.id=?
2019-09-16 13:55:14.781  INFO 21456 --- [           main] o.s.t.c.t.TransactionContext             : Rolled back transaction for test: [DefaultTestContext@7a22302c testClass = Java8RepoTests, testInstance = com.test.api.Java8RepoTests@4af50246, testMethod = testOne@Java8RepoTests, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@45482f82 testClass = Java8RepoTests, locations = '{}', classes = '{class com.test.api.ApiApplication}', contextInitializerClasses = '[class com.test.api.Java8RepoTests$Initializer]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5af3afd9, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5ffead27, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@1fa268de, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@222545dc], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]
2019-09-16 13:55:14.794  INFO 21456 --- [           main] o.s.t.c.t.TransactionContext             : Began transaction (1) for test context [DefaultTestContext@7a22302c testClass = Java8RepoTests, testInstance = com.test.api.Java8RepoTests@34a99d8, testMethod = testTwo@Java8RepoTests, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@45482f82 testClass = Java8RepoTests, locations = '{}', classes = '{class com.test.api.ApiApplication}', contextInitializerClasses = '[class com.test.api.Java8RepoTests$Initializer]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5af3afd9, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5ffead27, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@1fa268de, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@222545dc], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@448fa659]; rollback [true]
Hibernate: select user0_.id as id1_11_, user0_.created_date as created_2_11_, user0_.updated_date as updated_3_11_, user0_.created_by as created_4_11_, user0_.last_modified_by as last_mod5_11_, user0_.active as active6_11_, user0_.email as email7_11_, user0_.enabled as enabled8_11_, user0_.first_name as first_na9_11_, user0_.is_using2fa as is_usin10_11_, user0_.last_name as last_na11_11_, user0_.password as passwor12_11_ from users user0_
Hibernate: select roles0_.user_id as user_id1_10_0_, roles0_.role_id as role_id2_10_0_, role1_.id as id1_7_1_, role1_.created_date as created_2_7_1_, role1_.updated_date as updated_3_7_1_, role1_.title as title4_7_1_ from user_role roles0_ inner join role role1_ on roles0_.role_id=role1_.id where roles0_.user_id=?
Hibernate: select credential0_.user_id as user_id1_9_0_, credential0_.credential_id as credenti2_9_0_, credential1_.id as id1_2_1_, credential1_.created_date as created_2_2_1_, credential1_.updated_date as updated_3_2_1_, credential1_.created_by as created_4_2_1_, credential1_.last_modified_by as last_mod5_2_1_, credential1_.cloud_provider_id as cloud_pr8_2_1_, credential1_.access_key as access_k6_2_1_, credential1_.owner_id as owner_id9_2_1_, credential1_.secret_key as secret_k7_2_1_, credential1_.virtual_datacenter_id as virtual10_2_1_, cloudprovi2_.id as id1_1_2_, cloudprovi2_.created_date as created_2_1_2_, cloudprovi2_.updated_date as updated_3_1_2_, cloudprovi2_.name as name4_1_2_, user3_.id as id1_11_3_, user3_.created_date as created_2_11_3_, user3_.updated_date as updated_3_11_3_, user3_.created_by as created_4_11_3_, user3_.last_modified_by as last_mod5_11_3_, user3_.active as active6_11_3_, user3_.email as email7_11_3_, user3_.enabled as enabled8_11_3_, user3_.first_name as first_na9_11_3_, user3_.is_using2fa as is_usin10_11_3_, user3_.last_name as last_na11_11_3_, user3_.password as passwor12_11_3_, virtualdat4_.id as id1_13_4_, virtualdat4_.created_date as created_2_13_4_, virtualdat4_.updated_date as updated_3_13_4_, virtualdat4_.created_by as created_4_13_4_, virtualdat4_.last_modified_by as last_mod5_13_4_, virtualdat4_.blueprint_id as blueprin7_13_4_, virtualdat4_.name as name6_13_4_, virtualdat4_.organization_id as organiza8_13_4_, virtualdat4_.owner_id as owner_id9_13_4_, blueprint5_.id as id1_0_5_, blueprint5_.created_date as created_2_0_5_, blueprint5_.updated_date as updated_3_0_5_, blueprint5_.name as name4_0_5_, organizati6_.id as id1_3_6_, organizati6_.created_date as created_2_3_6_, organizati6_.updated_date as updated_3_3_6_, organizati6_.created_by as created_4_3_6_, organizati6_.last_modified_by as last_mod5_3_6_, organizati6_.name as name6_3_6_, organizati6_.user_id as user_id7_3_6_, user7_.id as id1_11_7_, user7_.created_date as created_2_11_7_, user7_.updated_date as updated_3_11_7_, user7_.created_by as created_4_11_7_, user7_.last_modified_by as last_mod5_11_7_, user7_.active as active6_11_7_, user7_.email as email7_11_7_, user7_.enabled as enabled8_11_7_, user7_.first_name as first_na9_11_7_, user7_.is_using2fa as is_usin10_11_7_, user7_.last_name as last_na11_11_7_, user7_.password as passwor12_11_7_ from user_credentials credential0_ inner join credentials credential1_ on credential0_.credential_id=credential1_.id inner join cloud_providers cloudprovi2_ on credential1_.cloud_provider_id=cloudprovi2_.id inner join users user3_ on credential1_.owner_id=user3_.id inner join virtual_datacenters virtualdat4_ on credential1_.virtual_datacenter_id=virtualdat4_.id inner join blueprints blueprint5_ on virtualdat4_.blueprint_id=blueprint5_.id inner join organizations organizati6_ on virtualdat4_.organization_id=organizati6_.id inner join users user7_ on virtualdat4_.owner_id=user7_.id where credential0_.user_id=?
Hibernate: insert into users (created_date, updated_date, created_by, last_modified_by, active, email, enabled, first_name, is_using2fa, last_name, password) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: select user0_.id as id1_11_, user0_.created_date as created_2_11_, user0_.updated_date as updated_3_11_, user0_.created_by as created_4_11_, user0_.last_modified_by as last_mod5_11_, user0_.active as active6_11_, user0_.email as email7_11_, user0_.enabled as enabled8_11_, user0_.first_name as first_na9_11_, user0_.is_using2fa as is_usin10_11_, user0_.last_name as last_na11_11_, user0_.password as passwor12_11_ from users user0_
Hibernate: select user0_.id as id1_11_, user0_.created_date as created_2_11_, user0_.updated_date as updated_3_11_, user0_.created_by as created_4_11_, user0_.last_modified_by as last_mod5_11_, user0_.active as active6_11_, user0_.email as email7_11_, user0_.enabled as enabled8_11_, user0_.first_name as first_na9_11_, user0_.is_using2fa as is_usin10_11_, user0_.last_name as last_na11_11_, user0_.password as passwor12_11_ from users user0_
2019-09-16 13:55:14.886  INFO 21456 --- [           main] o.s.t.c.t.TransactionContext             : Rolled back transaction for test: [DefaultTestContext@7a22302c testClass = Java8RepoTests, testInstance = com.test.api.Java8RepoTests@34a99d8, testMethod = testTwo@Java8RepoTests, testException = java.lang.AssertionError: expected:<1> but was:<2>, mergedContextConfiguration = [WebMergedContextConfiguration@45482f82 testClass = Java8RepoTests, locations = '{}', classes = '{class com.test.api.ApiApplication}', contextInitializerClasses = '[class com.test.api.Java8RepoTests$Initializer]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5af3afd9, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@5ffead27, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@1fa268de, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@222545dc], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]
2019-09-16 13:55:16.077  INFO 21456 --- [       Thread-9] o.s.s.c.ThreadPoolTaskExecutor           : Shutting down ExecutorService 'applicationTaskExecutor'
2019-09-16 13:55:16.087  INFO 21456 --- [       Thread-9] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'

The log output shows the transaction is created and rolled back. However, the data from testOne is still in the database when testTwo executes.

Not sure where to go from here. Google results show some variations of the above test code which I have explored to no avail.

rboarman
  • 8,248
  • 8
  • 57
  • 87
  • what is `providesFindOneWithOptional` – Toerktumlare Sep 16 '19 at 18:38
  • I renamed "providesFindOneWithOptional" to "testOne" for brevity. It's just the name of the test function. Thank you for pointing that out. – rboarman Sep 16 '19 at 19:12
  • its hard to know what is what in the log if you rename things – Toerktumlare Sep 16 '19 at 19:54
  • You code doesnt match the log, you select to check number of entries, you then insert and then you check number of entries. That makes it, select, insert, select. so clearly something is wrong in the provided information from you. On a sidenote, transactional only rolls back when an exception is thrown, so your first test passes and that data will propagate to test two. Best practice is for each test to start by cleaning up, then insert its data, then assert, if a test fails, you can check the state of the test and what made it fail. So cleanup, insert, assert. – Toerktumlare Sep 16 '19 at 20:09
  • @ThomasAndolf I updated the log output to show exactly what was produced including the queries. I also don't think you are right about rolling back. Spring documentation (section 3.2.3 - Transaction Management) states that the data will be rolled back after each test. In addition, the log output shows that a rollback was initiated. https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testing-tx – rboarman Sep 16 '19 at 21:04
  • you didn't clearly state that you wanted the tests to automatically rollback for you. There are several things that can be wrong. Documentation states multiple things that you should read `Spring Boot provides a @SpringBootTest annotation, which can be used as an alternative to the standard spring-test @ContextConfiguration annotation when you need Spring Boot features.` So you pick one, you do not use both. – Toerktumlare Sep 16 '19 at 22:44
  • They provide a full example for you in the docs that you should take a look at https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testcontext-tx-enabling-transactions – Toerktumlare Sep 16 '19 at 22:49
  • you are creating an `EntityManager` outside of Spring, although not used you probably are using it in your own code. Remove the setup/teardown for the `EntityManager`. If you need one just inject it using `@PersistenceCOntext` so that you use the same as used by the transaction. – M. Deinum Sep 17 '19 at 07:04
  • 1
    Also you are using MySQL, which by default uses MyISAM tables (if you let hibernate generate the tables) which don't support transactions. See https://stackoverflow.com/questions/57904111/jpa-save-with-multiple-entities-not-rolling-back-when-inside-spring-transaction/57905338#57905338 on how to switch to InnoDB tables. – M. Deinum Sep 17 '19 at 07:06
  • @M.Deinum Switching to InnoDB worked. If you switch your comment to an answer I will accept it. – rboarman Sep 17 '19 at 19:01
  • 1
    @ThomasAndolf Thank you for your comments. They helped me clean up the code. – rboarman Sep 17 '19 at 19:02

0 Answers0