Search code examples
javajunith2

JUnit - No Spring Boot - Clean H2 Database between each test


I am having issue setting up H2 database for JUnit. Important to note - I am not using spring boot autoconfiguration.

I want to purge all data between each test. Test classes extend H2DatabaseConfig class that sets up H2 connection. And my plan is to purge all data with a @AfterAll annotation. I dont want to use deleteAll method, like full drop of data (rows and autoincrement reset).

At the moment, when i run each test individually everything is fine. But when i run all together i get error

jakarta.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.example.database.entity.Team]

I know its related to my parent class. Its related to the way i am recreating session/connection. But not sure how to fix it.

I appreciate any hint you can give. Thank you in advance

H2DatabaseConfig

public class H2DatabaseConfig {
    private static Server server;
    private static Connection connection;
    protected static EntityManagerFactory entityManagerFactory;
    private static EntityManager entityManager;
    @BeforeEach
    public void setUp() throws SQLException {
        server = Server.createTcpServer().start();
        connection = DriverManager.getConnection("jdbc:h2:mem:test;MODE=MYSQL", "root", "password");

        entityManagerFactory = Persistence.createEntityManagerFactory("entity-manager-test");
        entityManager = entityManagerFactory.createEntityManager();
        Container.initBeans(entityManager);
    }

    @AfterEach
    public void tearDown() throws SQLException {
        connection.close();
        server.stop();
    }
}

TestClass

class FormTeamAddControllerTest extends H2DatabaseConfig {

    private FormTeamAddController formTeamAddController;
    private TeamSavePort teamSavePort;

    @BeforeEach
    public void set() {
        formTeamAddController = new FormTeamAddController();
        teamSavePort = Container.getBean(TeamSavePort.class);
    }
    @Test
    public void shouldSaveTeam() {
        // when
        ValidatorResponse save = formTeamAddController.save("Lowe", "123");

        // then
        assertTrue(save.isOk());
        assertEquals(TeamInputValidator.msgTeamIsCreated("Lowe"), save.getMessage());
    }

    @Test
    public void shouldNotSaveTeamCauseDuplicateTeamName() {
        teamSavePort.save("Star", BigDecimal.valueOf(123));
        // when
        ValidatorResponse save = formTeamAddController.save("Star", "123");

        // then
        assertTrue(save.hasErrors());
        assertEquals(save.getErrors().get("name"), TeamInputValidator.errTeamNameExists());
    }

    @Test
    public void shouldNotSaveTeamCauseWrongName() {
        // when
        ValidatorResponse save1 = formTeamAddController.save(null, "123");
        ValidatorResponse save2 = formTeamAddController.save("  ", "123");
        ValidatorResponse save3 = formTeamAddController.save("", "123");

        // then
        assertTrue(save1.hasErrors());
        assertTrue(save2.hasErrors());
        assertTrue(save3.hasErrors());

        assertEquals(save1.getErrors().get("name"), TeamInputValidator.errTeamName());
        assertEquals(save2.getErrors().get("name"), TeamInputValidator.errTeamName());
        assertEquals(save3.getErrors().get("name"), TeamInputValidator.errTeamName());
    }

    @Test
    public void shouldNotSaveTeamCauseWrongMembershipFee() {
        // when
        ValidatorResponse save1 = formTeamAddController.save("Star", null);
        ValidatorResponse save2 = formTeamAddController.save("Star", "   ");
        ValidatorResponse save3 = formTeamAddController.save("Star", "");

        // then
        assertTrue(save1.hasErrors());
        assertTrue(save2.hasErrors());
        assertTrue(save3.hasErrors());

        assertEquals(TeamInputValidator.errTeamFee(), save1.getErrors().get("membershipPayment"));
        assertEquals(TeamInputValidator.errTeamFee(), save2.getErrors().get("membershipPayment"));
        assertEquals(TeamInputValidator.errTeamFee(), save3.getErrors().get("membershipPayment"));
    }

}

persistence.xml

    <persistence-unit name="entity-manager-test" transaction-type="RESOURCE_LOCAL">
        <class>com.example.database.entity.Team</class>

        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS test" />
            <property name="jakarta.persistence.jdbc.user"   value="root" />
            <property name="jakarta.persistence.jdbc.password" value="password" />

            <!-- Generate the database schema -->
            <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>

            <!-- Customize the schema generation behavior -->
            <property name="javax.persistence.schema-generation.create-source" value="metadata"/>
            <property name="javax.persistence.schema-generation.drop-source" value="metadata"/>
            <property name="javax.persistence.schema-generation.scripts.action" value="none"/>


        </properties>
    </persistence-unit>

Solution

  • I think you can use the @AfterEach annotation instead of @AfterAll to ensure that the cleanup is performed after each individual test, so in H2DatabaseConfig class change the @AfterEach like below :

    @AfterEach
    public void tearDown() throws SQLException {
        entityManager.clear(); //clear entities from the session
        connection.close();
        server.stop();
    }
    

    and edit the persistence.xml file to include the javax.persistence.schema-generation.database.action property with "drop-and-create" to drop and recreate the database schema for each test and ensuring a clean state before running the tests like below:

    <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
    

    update

    about your last comments,better to make sure that you are properly clearing and resetting the persistence context between tests,in your H2DatabaseConfig class, add the entityManager.clear() method call in the tearDown() method and verify that your tests are not sharing or reusing the same instances of entities.each test should create its own instances of entities to avoid conflicts!