Affects: 5.3.2

After upgrading from spring boot 2.3.x to 2.4.1, some nested tests are failing with an exception that was catched.

Code example:

// Service:
  public void createTodo(Todo todo) {
    try {
      todoRepository.save(todo);
    } catch (DataIntegrityViolationException e) {
      log.warn("Could not store todo ", e);
      logRepository.save(new Log(e.getMessage()));
    }
  }

// Test:
  @Nested
  class CreateTodo {

    @Test
    void checkThatErrorIsPersistedOnException() {
      service.createTodo(new Todo("way too long message > 10 characters"));
      // before spring boot 2.4.x this runs successful
      // after spring boot 2.4.x this throws the already catched exception
      assertThat(todoRepository.findAll()).isEmpty();
      assertThat(logRepository.findAll()).hasSize(1);
    }
  }


Test case: to execute https://github.com/knoobie/spring-nested-test

Changing the spring boot version to e.g. 2.3.5.RELEASE fixes the test.

Comment From: jnizet

Your test passed before because it relied on a bug, which has now been fixed, of nested Spring tests: they weren't inheriting the annotations of the outer test class.

DataJpaTest-annotated tests are transactional. This means that each test method is run inside a transaction. But since your test is in a nested class, before Boot 2.4.0, the nested test was not transactional (because of the bug). So the call to the repository started a transaction and tried to commit it, thus causing the exception to be thrown at flush time (just before the commit), and causing the catch block to execute and start a second transaction to save the Log.

Since Sprng Boot 2.4.0, nested tests inherit the outer class annotations, and the nested test is thus transactional. So the call to the repository happens in the test transaction. you're not calling flush(), so the database insert is delayed and no exception is being thrown by save(). Then the tests executes a query, causing the delayed insert to be executed, and to throw the exception

To get back your previous behavior, you need to make the test not transactional, so that each repository call starts and commits its own transaction. Note however that, even if that makes the test pass, the actual code, in production, will have the exact same problem your test shows if the createTodo method is ever called in an existing transaction.

Comment From: knoobie

@jnizet Thanks for your valuable feedback! Closing the ticket.