A while ago, I wrote this article on the Hibernate blog which shows you how to create a test case which reproduces a certain Hibernate ORM issue.
In the meanwhile, I came to realize that there is a much better way which I will explain in this article.
The hibernate-test-case-templates drawbacks
Although the hibernate-test-case-templates
is much better than
having to come up with your own test case and figure out the bootstrapping part, there are several drawbacks to it:
-
Being hosted on GitHub, some developers submit their test cases as Pull Requests for the
hibernate-test-case-templates
instead of attaching them to the associate Hibernate JIRA issue. -
As a developer, you won’t benefit from our
doInJPA
ordoInHibernate
utilities we have developed for the Hibernate ORM suite of tests. -
Replicating a test case across various databases requires manually changing the database connectivity configuration properties.
-
The Hibernate ORM developers need to download the test case from the JIRA issue and manually integrate it to the Hibernate ORM project. However, this also means we cannot attribute the original work to the test case author.
The alternative
One alternative which could work better for both Hibernate ORM clients and the core developers is if test cases are submitted as Pull Requests
for the hibernate-orm
project itself.
Forking the repository
First, you need to fork the hibernate-orm
repository on GitHub.
That’s exactly how anyone contributing to the Hibernate ORM project work.
Setting up the project
You can find a very detailed explanation of how you can setup and build the hibernate-orm
project on
the main GitHub page.
If you are using IntelliJ IDEA is as simple as importing the project from Gradle.
Creating the test case
Now that you have set up the hibernate-orm
project, to create a test case you should extend from one of these
two base classes:
BaseEntityManagerFunctionalTestCase
::For JPA-based test cases.
BaseNonConfigCoreFunctionalTestCase
::For test cases requiring the Hibernate-specific bootstrap mechanism.
Let’s assume we want to create a test case for the HHH-12561 JIRA issue.
The first thing we need to do is to create a new branch:
git checkout -b HHH-12561
The test case can look as follows:
@TestForIssue( JIRAKey = "HHH-12561" )
public class GlobalQuotedIdentifiersBulkIdTest
extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Doctor.class,
Engineer.class
};
}
@Override
protected void addConfigOptions(Map options) {
options.put(
AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS,
Boolean.TRUE
);
options.put(
AvailableSettings.HQL_BULK_ID_STRATEGY,
InlineIdsOrClauseBulkIdStrategy.class.getName()
);
}
@Before
public void setUp() {
doInJPA( this::entityManagerFactory, entityManager -> {
for ( int i = 0; i < entityCount(); i++ ) {
Doctor doctor = new Doctor();
doctor.setEmployed( ( i % 2 ) == 0 );
doctor.setEmployedOn(
Timestamp.valueOf( "2018-06-01 00:00:00" )
);
entityManager.persist( doctor );
}
for ( int i = 0; i < entityCount(); i++ ) {
Engineer engineer = new Engineer();
engineer.setEmployed( ( i % 2 ) == 0 );
engineer.setEmployedOn(
Timestamp.valueOf( "2018-06-01 00:00:00" )
);
engineer.setFellow( ( i % 2 ) == 1 );
entityManager.persist( engineer );
}
});
}
protected int entityCount() {
return 5;
}
@Test
public void testBulkUpdate() {
doInJPA( this::entityManagerFactory, entityManager -> {
int updateCount = entityManager.createQuery(
"UPDATE Person u " +
"SET u.employedOn = :date " +
"WHERE u.id IN :userIds"
)
.setParameter( "date", Timestamp.valueOf( "2018-06-03 00:00:00" ) )
.setParameter( "userIds", Arrays.asList(1L, 2L, 3L ) )
.executeUpdate();
assertEquals(3, updateCount);
});
}
@Entity(name = "Person")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Person {
@Id
@GeneratedValue
private Long id;
private String name;
private boolean employed;
@Temporal( TemporalType.TIMESTAMP )
private Date employedOn;
//Getters and setters omitted for brevity
}
@Entity(name = "Doctor")
public static class Doctor extends Person {}
@Entity(name = "Engineer")
public static class Engineer extends Person {
private boolean fellow;
//Getters and setters omitted for brevity
}
}
There are several methods which we have overridden from the base class, like:
getAnnotatedClasses
-
Which defines the entity classes we want this test case to use.
addConfigOptions
-
Which defines the additional configuration properties we’d like to provide to Hibernate.
The setUp
method uses the @Before
JUnit annotation, and so it will be called before every test execution.
The testBulkUpdate
method defines the test logic which replicates the Hibernate issue in question.
Notice the use of the doInJPA
methods which hide all the logic for creating an EntityManager
,
starting a transaction, committing it and closing the EntityManager
.
All you need to do is to provide the data access logic.
Testing on various RDBMS
One of the best advantages of using the actual Hibernate ORM tets suite is that you can easily switch the underlying database.
For instance, to run this test on PostgreSQL, just run the following Gradle command from the hibernate-core
folder:
gradlew test --tests org.hibernate.test.bulkid.GlobalQuotedIdentifiersBulkIdTest -Pdb=pgsql
Or, if you want to run this test on a specific database right from your IDE make sure you setup the current database using the following command
from within the Hibernate module folder the test is located (e.g. hibernate-core
):
gradlew sDB -Pdb=pgsql
Now, when you run the GlobalQuotedIdentifiersBulkIdTest
from your IDE, the test will run on PostgreSQL.
In
hibernate-orm/gradle/databases.gradle
configuration file, you can find and customize the database profiles that you can pass via the-Pdb
Gradle property.
Awesome stuff!
Committing the test case
Now, we can commit all changes:
git commit -am "HHH-12561 - bulk_id_strategy does not work with globally_quoted_identifiers"
Next, we need to push the local branch to our fork:
git push origin HHH-12561
Now, go to your hibernate-orm
fork page on GitHUb and press the Compare and pull request
button:
Now, add a commit message, choose the JIRA issue Test Case
label, and click the Click Pull Request
button:
That’s it!
You’ve just submitted your test case to the hibernate-orm
project which can be integrated along with the fix.
Conclusion
As you can see, providing a test case for the hibernate-orm
project as a GitHub Pull Request is even easier
than working with the hibernate-test-case-templates
.
Therefore, if you spot a Hibernate issue, prior to creating the Hibernate JIRA issue, please provide a test case which reproduces the issue in question.