'Does @Transactional annotation on test methods rollback every transaction in the annotated method before it gets to the end?

I am writing a test which confirms that trying to create a user with an email which already exists results in a failure. The database has been set up to have a unique constraint on the email field. I am using an actual database to test this.

    @Test
    @DisplayName("Trying to create a user with an email which already exists should fail")
    void createAUserWithAnEmailWhichIsAlreadyTakenByAnotherUser() {
        User firstUser = createDummyUser();
        firstUser = saveOrUpdate(firstUser);
        Assertions.assertNotNull(firstUser);

        String existingEmail = firstUser.getEmail();

        User secondUser = createDummyUser();
        secondUser.setEmail(existingEmail);
        Assertions.assertThrows(DataIntegrityViolationException.class, () -> userService.save(secondUser));
    }

My question is the following:

  1. When I put the @Transactional annotation on either the test class or the test method. This test fails and the expected exception is not thrown. Why is this ? In my mind the way transactional is supposed to work is that it will rollback all the transactions after the method has finished executing ?

The reason I want to clean the DB after every test is because I can't risk some test failing because of validations I have in place(like this unique email validation).



Solution 1:[1]

The reason why that happens is that when using the @Transactional annotation, the changes are not committed to the database until the entire method is completed. Meaning, the commit to the database doesn't happen until after the assertion you make, so the DataIntegrityViolationException has not occurred yet.

Hence, as Vladimir mentioned in the comment, inject an EntityManager and call its flush after the entities are saved to the database. This way you force the changes to be committed to the database, and the exception should be thrown upon the flush call.

    @Autowired private EntityManager entityManager;

    @Test
    @DisplayName("Trying to create a user with an email which already exists should fail")
    void createAUserWithAnEmailWhichIsAlreadyTakenByAnotherUser() {
        User firstUser = createDummyUser();
        firstUser = saveOrUpdate(firstUser);
        Assertions.assertNotNull(firstUser);

        String existingEmail = firstUser.getEmail();

        User secondUser = createDummyUser();
        secondUser.setEmail(existingEmail);

        userService.save(secondUser)
        Assertions.assertThrows(DataIntegrityViolationException.class, () -> entityManager.flush());
    }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1