Simplifying Drools Tests with AssertJ

Introduction

Creating clean, concise, and maintainable tests is crucial for any software project. In this post, we will discuss the ongoing refactoring process for Drools tests and illustrate how these techniques can benefit other projects. By leveraging the expressiveness of AssertJ assertions, we aim to enhance readability and conciseness, leading to more efficient and easily comprehensible tests.

The Original Code

The code used in this example comes from the drools project.

The test class is BackwardChainingTest and the test method is testQueryWithOr.

The code can be retrieved from here: BackwardChainingTest.java

In the original tests the block we are considering uses roughly 55 lines of code.

Let’s start by examining a snippet from the original code:

List<Integer> list = new ArrayList<>();
Query Results results = ksession.getQueryResults("p", new Integer[]{2});
for (final QueryResultsRow result : results) {
    list.add((Integer) result.get("x"));
}
assertThat(list.size()).isEqualTo(1);
assertThat(list.get(0).intValue()).isEqualTo(2);

This code retrieves query results from a Drools knowledge session (ksession) and iterates through the results to add them to a list. It then asserts the list size and content. 

While this code works, it’s not as concise or expressive as it could be.

Let’s see how we can improve this code.

Step 1 – Use AssertJ assertions

The snippet presented before uses AssertJ assertions. We can improve the code by leveraging the assertions for collections of AssertJ.

List<Integer> list = new ArrayList<>();
QueryResults results = ksession.getQueryResults("p", new Integer[]{2});
for (final QueryResultsRow result : results) {
    list.add((Integer) result.get("x"));
}
assertThat(list).hasSize(1); 
assertThat(list).contains(2);

These assertions are now clearer and simpler, but they can be simplified more. The first assertion states that list contains only one element, and the second assertion states that list contains exactly the element 2. So we can rewrite this snippet as follow:

List<Integer> list = new ArrayList<>();
QueryResults results = ksession.getQueryResults("p", new Integer[]{2});
for (final QueryResultsRow result : results) {
    list.add((Integer) result.get("x"));
}
assertThat(list).containsExactly(2); 

Step 2 – Create collections on the fly using extracting

This is already simpler and better, however there is still the rather ugly code represented by the creation of the list collection. We are creating the list collection just for the assertion.

Fortunately AssertJ has a very powerful mechanism to create collections on the fly and use them for assertions, named extracting. With extracting it is possible to take a collection, extract one or more properties from all the elements of the collections and then use the resulting collection to perform assertions.

Here is the code using extracting:

QueryResults results = ksession.getQueryResults("p", new Integer[]{2});
assertThat(results).extracting(r -> r.get("x")).containsExactly(2); 

We have now removed the code for initializing the variable list, and we are down only two lines.

Step 3 – Inline variable in a single assertion

Looking at the getQueryResult method of the session, it is clear that it already takes a vararg argument, so it is not necessary to use an array.

QueryResults results = ksession.getQueryResults("p", 2);
assertThat(results).extracting(r -> r.get("x")).containsExactly(2);

As an optional step, we can remove the array and inline the result variable, obtaining a much more compacted version of our original assertion:

assertThat(ksession.getQueryResults("p",2)).extracting(r -> r.get("x")).containsExactly(2); 

This is marked optional since some people prefer to keep intermediate variables on separate lines for debugging purposes.

Step 4 – The final result

Repeating the process for all the original 55 lines of codes in the test produces the final result:

assertThat(ksession.getQueryResults("p", 0)).extracting(r -> r.get("x")).isEmpty();
assertThat(ksession.getQueryResults("p", 1)).extracting(r -> r.get("x")).containsExactly(1);
assertThat(ksession.getQueryResults("p", 2)).extracting(r -> r.get("x")).containsExactly(2);
assertThat(ksession.getQueryResults("p", 3)).extracting(r -> r.get("x")).containsExactly(3);
assertThat(ksession.getQueryResults("p", 4)).extracting(r -> r.get("x")).isEmpty();
assertThat(ksession.getQueryResults("p", 5)).extracting(r -> r.get("x")).isEmpty();
assertThat(ksession.getQueryResults("p", 6)).extracting(r -> r.get("x")).containsExactly(6, 6);

This uses only 7 lines of codes and it is much more clear than the original version.

Conclusions

In our view the new version of the code is better for several reasons:

  • Conciseness: The code is simplified, making it easier to understand at a glance.
  • Readability: The new version is more expressive, making it clear what the test is asserting.
  • AssertJ Capabilities: By leveraging the AssertJ library, you can take advantage of its powerful assertions and extraction methods, leading to more maintainable and flexible tests.

We hope you can reuse part or all of these patterns to improve the tests of other projects. 

5 1 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments