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>
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!