Search code examples
spring-bootjunitjhipsterspring-transactionsmaven-surefire-plugin

maven-surefire-plugin runs single method, but failed on class


I wrote test that require transactions, it looks like :

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ExchangeApp.class)
@EnableTransactionManagement(proxyTargetClass = true, mode = AdviceMode.PROXY)
@ActiveProfiles({JHipsterConstants.SPRING_PROFILE_TEST})
public abstract class AbstractServiceTest {

So when I run single test method : mvn test -Dtest=TestClassName#method1 works as expected, but

mvn test  -Dtest=TestClassName

failed, with weird exceptions, exception says constraint violation in @OneToMany and exceptions with decimal calculations during divide in BigDecimal. Same exceptions when I run in IDE. It looks like transaction managing missed. Any ideas ?

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["FK_OPEN_EXEC_ID: PUBLIC.ORDER_PAIR_OPEN_EXEC FOREIGN KEY(EXECUTIONS_ID) REFERENCES PUBLIC.ORDER_PAIR_OPEN(ID) (2)"; SQL statement:
delete from order_pair_open where id=? [23503-197]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement

UPD: also I already tried

 <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven-surefire-plugin.version}</version>
                <configuration>
                    <!-- Force alphabetical order to have a reproducible build -->
                    <runOrder>alphabetical</runOrder>
                    <parallel>classes</parallel>
                    <threadCountClasses>1</threadCountClasses>
                    <threadCountMethods>1</threadCountMethods>
                    <threadCountSuites>1</threadCountSuites>
                </configuration>
            </plugin>

UPD: it is specific to my case. I am trying to test service with @Async method inside, so it seems I have to mark @Transactional on test method name, in order to enable transaction support, thats why I tried to use @EnableTransactionManagement(proxyTargetClass = true, mode = AdviceMode.PROXY) to enable transactions managing during class test. Here it is pseudo code :

 class Service1Test extends AbstractServiceTest {
    Service1 service1;
    Repo1 repo1;

    //Does not works with class call, but works with method call
    //when I mark this method with @Transactional, mentioned exceptions are gone, 
    // but I cant check result since "registerSynchronization" were not called
    @Test
    public void test1() throws InterruptedException {
        service1.method1();
        synchronized (this) {
            wait(2000l);
        }

        assertThat( repo1.findAll().size()).isEqualTo(1);
        //repoN check
    }
}

@Service
@Transactional
class Service1 {
    Service2 service2;


    @Async
    public void method1() {
        //DB operations...
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                service2.method2();
            }

        });

    }

}

@Service
class Service2 {
    Repo1 repo1;
    public void method2() {
        repo1.save(new Entity1());
    }

}

@Service
class Service3 {
    @Autowired
    private ScheduledExecutorService scheduler;

    public void method3() {
        scheduler.schedule(() -> {
            //other transactional  services  call
        }, 1l, TimeUnit.SECONDS);
    }
}
@Repository
interface Repo1  extends JpaRepository<Entity1, Long> {

}
@Entity
class Entity1{

}

Solution

  • I can't comment on JHipster side, but one possible reason is that "Test Transactional" behavior is not applied to the test code.

    Just to clarify, by default if you make a test as @Transactional, spring opens one transaction, the test runs, and when its done (regardless whether it has passed or failed) the transaction does the rollback effectively cleaning up the database.

    Now, I don't see this in tests, so probably you don't use this behavior. But this is pure spring, not a spring boot.

    Now regarding the spring boot part.

    If you use @SpringBootTest with a concrete configuration ExchangeAppin this case, the chances are that it won't load any autoconfiguration-s (for example those that define configurations working with transactions, data source management, etc.).

    If you want to "mimic" the load of the microservice, you should run @SpringBootTest without configurations but it's beyond the scope of the question.

    A "by the book" spring boot way to test DAO with hibernate is using @DataJpaTest that loads only the database related stuff, but it can't be used with @SpringBootTest - you should choose one.

    So for me it's clear that the test does something tricky and definitely not something that follows spring boot conventions, so probably spring/spring boot strikes back :)

    Now, regarding the Asynchronous stuff. This can also contribute to the mess because transaction support in spring relies heavily on Thread Local concept, so when the new thread gets executed (on another thread pool or something) the information about transaction does not propagate, so spring can't understand that its still in the same transaction. I see that you use the TransactionSynchronizationManager but without debugging its hard to tell what happens.

    Now in order to check why doesn't the transaction get propagated, I think you should debug the application and see:

    • Whether the services are wrapped in proxy that support transactions (that's what @Transactional does, assuming the relevant BeanPostProcessor was applied)
    • Check that during each step you're in the transaction
    • Consider using @Transactional on test / test case, so that it would clean up the changes that have been applied during the test