Skip to content
Advertisement

Data from test with @Transactional are persisting between tests

I have a small problem. I have started writing tests for my small project. The project uses SpringBoot, standard JpaRepository from Spring, as a testing framework I am using Spock and for testing the database, I’m using PostgreSQL container from TestContainers. The problem is, that data between tests is being persisted, despite the @Transacional on each of the tests. The strangest part is, that in the logs I can see, that transaction is rolled back. I would appreciate any help.

So, these are the files:

  1. File with a shared container for tests, that all integration tests should extend from:
@SpringBootTest(classes = DatabaseMain.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class BaseTestIT extends Specification {
    private static final Logger LOG = LoggerFactory.getLogger(BaseTestIT.class);
    @Shared
    private static final PostgreSQLContainer POSTGRES_CONTAINER = new PostgreSQLContainer("postgres")
            .withDatabaseName('test').withUsername(
            'test').withPassword('test');
...
}
  1. File with configuration:
@Configuration
@PropertySource("database.properties")
public class ConfigurationIT {
    private final String databaseUrl;
    private final String databaseDialect;
    private final String databaseType;

    @Autowired
    public ConfigurationIT(@Value("${test.database.url}") String databaseUrl,
            @Value("${test.database.dialect}") String databaseDialect,
            @Value("${test.database.type}") String databaseType) {
        this.databaseDialect = databaseDialect;
        this.databaseUrl = databaseUrl;
        this.databaseType = databaseType;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
        entityManagerFactoryBean.setPersistenceUnitName("testIT");
        entityManagerFactoryBean.setPackagesToScan("com.testproject");
        Map<String, String> properties = new HashMap<>();
        properties.put("spring.jpa.database", this.databaseType);
        properties.put("spring.jpa.database-platform", this.databaseDialect);
        properties.put("hibernate.dialect", this.databaseDialect);
        properties.put("hibernate.connection.url", this.databaseUrl);
        entityManagerFactoryBean.setJpaPropertyMap(properties);
        return entityManagerFactoryBean;
    }

    @Bean
    @Autowired
    public JpaTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean emf) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf.getObject());
        return transactionManager;
    }
}
  1. application.yaml
spring:
  datasource:
    driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
    url: ${test.database.url}
    username: ${test.database.username}
    password: ${test.database.password}
  jpa:
    database-platform: ${test.database.dialect}
    database: ${test.database.type}
  1. and test file:
@SpringBootTest(classes = DatabaseMain.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@Testcontainers
class ArchitectTestIT extends BaseTestIT {
    private static final String NAME = "name";
    private static final String LAST_NAME = "last_name";

    @Autowired
    private TestRestTemplate restTemplate;
    @Autowired
    private ArchitectRepository architectRepository;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    def "Creating Architect with proper DTO should return created architect and 201 code"() {
        given: "proper architect dto"
            ArchitectBasicDto architectBasicDto = new ArchitectBasicDto();
            architectBasicDto.setFirstName(NAME);
            architectBasicDto.setLastName(LAST_NAME);
            HttpEntity entity = new HttpEntity(architectBasicDto);
        when: "passed to architect post method"
            def exchange = this.restTemplate.exchange("/architects", HttpMethod.POST, entity, ArchitectBasicDto.class);
        then: "returns code 201 with dto of created Architected with location in the header"
            exchange.statusCode == HttpStatus.CREATED;
        and:
            exchange.getBody().getFirstName() == NAME;
        and:
            exchange.getBody().getLastName() == LAST_NAME;
        and:
            def id = exchange.getBody().getId()
            id != null;
        and:
            exchange.getHeaders().get("Location").get(0) == "/architects/" + id;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    def "Creating Architect with not proper DTO should return code 400 and not create new Architect"() {
        given:
            ArchitectBasicDto architectBasicDto = new ArchitectBasicDto();
            architectBasicDto.setFirstName(null);
            architectBasicDto.setLastName(LAST_NAME);
            HttpEntity entity = new HttpEntity(architectBasicDto);
        when:
            def exchange = this.restTemplate.exchange("/architects", HttpMethod.POST, entity, ArchitectBasicDto.class);
        then:
            exchange.statusCode == HttpStatus.BAD_REQUEST;
        and:
            exchange.getBody().getId() == null;
        amd:
            exchange.getBody().getFirstName() == null;
        and:
            exchange.getBody().getLastName() == null;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    def "Removing existing Architect will return code 200 and remove it from database"() {
        given:
            ArchitectBasicDto architectBasicDto5 = new ArchitectBasicDto();
            architectBasicDto5.setFirstName(NAME);
            architectBasicDto5.setLastName(LAST_NAME);
            HttpEntity entity5 = new HttpEntity(architectBasicDto5);
            ResponseEntity<ArchitectBasicDto> exchange5 =
                    this.restTemplate.exchange("/architects", HttpMethod.POST, entity5, ArchitectBasicDto.class);
            HttpEntity entity = new HttpEntity(null);
        when:
            ResponseEntity<ArchitectBasicDto> exchange =
                    this.restTemplate.exchange("/architects/" + exchange5.getBody().getId(), HttpMethod.DELETE, entity,
                            ArchitectBasicDto.class);
        then:
            exchange.statusCode == HttpStatus.OK;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    def "Removing not existing Architect will give code 400 and error message about not existing Architect"() {
        given:
            HttpEntity entity = new HttpEntity(null);
        when:
            ResponseEntity<Map<String, String>> exchange =
                    this.restTemplate.exchange("/architects/" + 2000, HttpMethod.DELETE, entity,
                            Map<String, String>.class);
        then:
            exchange.statusCode == HttpStatus.BAD_REQUEST;
        and:
            exchange.getBody().get("message") == "Architect with id 2,000 does not exist.";
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    def "Calling get on /architects should return all architects basic dto"() {
        given:
            HttpEntity entity = new HttpEntity(null);
        when:
            ResponseEntity<List<ArchitectBasicDto>> exchange =
                    this.restTemplate.exchange("/architects", HttpMethod.GET, entity,
                            List<ArchitectBasicDto>.class);
        then:
            exchange.getBody().size() == 0;
    }
}

The last test was a bit modified, just to show the problem. As I do not save any of the Entities, it should pass, while it does not:

Condition not satisfied:

exchange.getBody().size() == 0
|        |         |      |
|        |         1      false
|        [[id:100, firstName:name, lastName:last_name]]
<200,[{id=100, firstName=name, lastName=last_name}],[Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Thu, 16 Dec 2021 20:55:38 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]>

And as for logs, they show that transactions are being rolled back:

[2021-12-16T21:55:38,337]  INFO [main][] - TransactionContext.startTransaction(TransactionContext.java:107) - Began transaction (1) for test context [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@5a30ab46, testMethod = $spock_feature_1_0@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4df4f611]; rollback [true]
[2021-12-16T21:55:38,589]  INFO [http-nio-8080-exec-1][] - DirectJDKLog.log(DirectJDKLog.java:173) - Initializing Spring DispatcherServlet 'dispatcherServlet'
[2021-12-16T21:55:38,589]  INFO [http-nio-8080-exec-1][] - FrameworkServlet.initServletBean(FrameworkServlet.java:525) - Initializing Servlet 'dispatcherServlet'
[2021-12-16T21:55:38,591]  INFO [http-nio-8080-exec-1][] - FrameworkServlet.initServletBean(FrameworkServlet.java:547) - Completed initialization in 2 ms
[2021-12-16T21:55:38,745]  INFO [main][] - TransactionContext.endTransaction(TransactionContext.java:139) - Rolled back transaction for test: [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@5a30ab46, testMethod = $spock_feature_1_0@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
[2021-12-16T21:55:38,749]  INFO [main][] - TransactionContext.startTransaction(TransactionContext.java:107) - Began transaction (1) for test context [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@317e9cf5, testMethod = $spock_feature_1_1@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4df4f611]; rollback [true]
[2021-12-16T21:55:38,759] ERROR [http-nio-8080-exec-2][] - GlobalExceptionHandler.handleException(GlobalExceptionHandler.java:33) - isNull.architect.firstName
com.testproject.task.sharedkernel.exceptions.IllegalArgumentException: isNull.architect.firstName
    at com.testproject.task.sharedkernel.exceptions.BaseValidator.assertIsTrue(BaseValidator.java:36) ~[classes/:?]
    at com.testproject.task.architect.application.ArchitectValidator.validateName(ArchitectValidator.java:50) ~[classes/:?]
    at com.testproject.task.architect.application.ArchitectValidator.validateBasicArchitectDto(ArchitectValidator.java:38) ~[classes/:?]
    at com.testproject.task.architect.application.impl.ArchitectApplicationServiceImpl.createArchitect(ArchitectApplicationServiceImpl.java:41) ~[classes/:?]
    at com.testproject.task.architect.application.impl.ArchitectApplicationServiceImpl$$FastClassBySpringCGLIB$$e4b5b2ac.invoke(<generated>) ~[classes/:?]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.8.jar:5.3.8]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) ~[spring-aop-5.3.8.jar:5.3.8]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.8.jar:5.3.8]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.8.jar:5.3.8]
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.8.jar:5.3.8]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.8.jar:5.3.8]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.8.jar:5.3.8]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.8.jar:5.3.8]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.8.jar:5.3.8]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) ~[spring-aop-5.3.8.jar:5.3.8]
    at com.testproject.task.architect.application.impl.ArchitectApplicationServiceImpl$$EnhancerBySpringCGLIB$$e31df751.createArchitect(<generated>) ~[classes/:?]
    at com.testproject.task.architect.rest.ArchitectRestController.createArchitect(ArchitectRestController.java:36) ~[classes/:?]
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[?:?]
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
    ...
    at java.lang.Thread.run(Thread.java:832) [?:?]
[2021-12-16T21:55:38,810]  INFO [main][] - TransactionContext.endTransaction(TransactionContext.java:139) - Rolled back transaction for test: [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@317e9cf5, testMethod = $spock_feature_1_1@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]
[2021-12-16T21:55:38,818]  INFO [main][] - TransactionContext.startTransaction(TransactionContext.java:107) - Began transaction (1) for test context [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@9b8d3db, testMethod = $spock_feature_1_2@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4df4f611]; rollback [true]
[2021-12-16T21:55:39,054]  INFO [main][] - TransactionContext.endTransaction(TransactionContext.java:139) - Rolled back transaction for test: [DefaultTestContext@4a8d8f50 testClass = ArchitectTestIT, testInstance = com.testproject.task.architect.application.ArchitectTestIT@9b8d3db, testMethod = $spock_feature_1_2@ArchitectTestIT, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@7772549d testClass = ArchitectTestIT, locations = '{}', classes = '{class com.testproject.task.DatabaseMain}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.spockframework.spring.mock.SpockContextCustomizer@0, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@628c4ac0, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@363042d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@447a020, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@56113384, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@41dd05a, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@39ba5a14], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.spockframework.spring.SpringMockTestExecutionListener.MOCKED_BEANS_LIST' -> list[[empty]], 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]

So I’m a bit stuck, as for what could be the reason why those are not actually being rolled back and I would appreciate any help with that. Thanks!

Advertisement

Answer

Your tests start an app that listens on a real port. And you use TestRestTemplate to make HTTP calls. It’s the same as if you ran your test from a remote machine – would you expect @Transactional on such tests to be some how applied to the app?

@Transactional will work only if you invoke your endpoint directly, without any network calls:

  • either inject endpoint object directly to your test and call its method
  • or use MockMvc (or RestAssured+MockMvc) – it will also eventually call the endpoint directly

Both of these options will simplify debugging – you’ll be able to see in call stack which test is calling your production code at the moment.

PS: also it shouldn’t be a problem when your data is kept between test runs. You can isolate your tests with randomization.

Advertisement