Practice: Improve Test Expressiveness
Ready to practice improving test expressiveness with AssertJ? We've prepared a test class with verbose JUnit assertions that you can refactor.
Exercise: DiscountCalculatorTest
The DiscountCalculatorTest class in the orders module demonstrates common assertion anti-patterns:
What you'll find
- ❌ Verbose JUnit assertions:
assertEquals,assertTrue,assertNotNull - ❌ Manual BigDecimal comparisons: Using
compareTo()in assertions
Your task
Refactor the tests to use AssertJ for better expressiveness.
Hints:
- AssertJ uses a fluent API that starts with
assertThat(actualValue). - Look at the Poor expressiveness examples for patterns.
- BigDecimal comparisons have special assertion methods that are more readable than
compareTo(). - Multiple assertions on the same object can be chained together.
- The
.as()method can add descriptive messages to your assertions. - Check the reference table below for common patterns.
Running the test
You can run the test in your IDE or use the following commands:
# Run the test
mvn test -pl orders -Dtest=DiscountCalculatorTest
# Or run all tests in the orders module
mvn test -pl orders
Automated refactoring
You can use OpenRewrite to automatically migrate JUnit assertions to AssertJ. See the AssertJ Best Practices page for detailed instructions on running the recipe.
What you'll learn
By completing this exercise, you'll gain hands-on experience with:
- Converting verbose JUnit assertions to expressive AssertJ assertions.
- Using AssertJ's fluent API for better readability.
- Handling BigDecimal comparisons elegantly.
- Writing assertions that clearly convey test intent.
- Creating better failure messages.
Click to see the refactored test
DiscountCalculatorTest.java
package com.github.timtebeek.orders;
import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigDecimal;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class DiscountCalculatorTest {
private DiscountCalculator calculator;
private Customer silverCustomer;
private Customer goldCustomer;
@BeforeEach
void setUp() {
calculator = new DiscountCalculator();
Address address = new Address("123 Main St", "Springfield", "IL", "62701", "USA");
silverCustomer = new Customer("C002", "silver@example.com", "Silver User", address, "SILVER");
goldCustomer = new Customer("C003", "gold@example.com", "Gold User", address, "GOLD");
}
@Test
void goldCustomerDiscount() {
BigDecimal subtotal = new BigDecimal("300.00");
BigDecimal discount = calculator.calculateLoyaltyDiscount(goldCustomer, subtotal);
assertThat(discount)
.isEqualByComparingTo(new BigDecimal("45.00"))
.isGreaterThan(new BigDecimal("20.00"))
.isLessThan(new BigDecimal("50.00"));
}
@Test
void totalDiscountCombinesLoyaltyAndBulk() {
BigDecimal subtotal = new BigDecimal("600.00");
BigDecimal totalDiscount = calculator.calculateTotalDiscount(goldCustomer, subtotal);
// Gold customer gets 15% = 90.00
// Bulk discount gets 5% = 30.00
// Total should be 120.00
assertThat(totalDiscount)
.isEqualByComparingTo(new BigDecimal("120.00"))
.isBetween(new BigDecimal("100.00"), new BigDecimal("150.00"));
}
@Test
void discountScaleIsCorrect() {
BigDecimal subtotal = new BigDecimal("123.45");
BigDecimal discount = calculator.calculateLoyaltyDiscount(silverCustomer, subtotal);
assertThat(discount)
.hasScaleOf(2)
.asString()
.matches("\\d+\\.\\d{2}");
}
}
Key changes:
assertNotNull+assertEquals(0, x.compareTo(y))replaced withassertThat(x).isEqualByComparingTo(y)(scale-insensitive BigDecimal comparison)assertTrue(x.compareTo(y) > 0)replaced withassertThat(x).isGreaterThan(y)- Range checks combined using
isBetween() - Multiple assertions on the same value chained into a single fluent statement
- Redundant
assertNotNullcalls removed (AssertJ fails clearly on null)