Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Beyond JUnit 5

Beyond JUnit 5

Avatar for Jeanne Boyarsky

Jeanne Boyarsky

August 15, 2025
Tweet

More Decks by Jeanne Boyarsky

Other Decks in Programming

Transcript

  1. https://linktr.ee/jeanneboyarsky Agenda 5 • Readable Assertions • AssertJ • JUnit

    Pioneer • Mocking • Selenium • IDE Integration • Patterns/Techniques
  2. https://linktr.ee/jeanneboyarsky What does this output? 7 // public String getFavoriteActivity()

    { // return "swimming"; // } @Test void summerFunWithPureJUnit() { assertTrue(fun.getFavoriteActivity().startsWith("swim")); assertTrue(fun.getFavoriteActivity().startsWith("tennis")); } org.opentest4j.AssertionFailedError: Expected :true Actual :false
  3. https://linktr.ee/jeanneboyarsky Making this better (with JUnit) 8 @Test void summerFunWithReadableOutput()

    { assertTrue(fun.getFavoriteActivity().startsWith("swim"), "expected to start with 'swim' but was " + fun.getFavoriteActivity()); assertTrue(fun.getFavoriteActivity().startsWith("tennis"), "expected to start with 'tennis' but was " + fun.getFavoriteActivity()); } org.opentest4j.AssertionFailedError: expected to start with 'tennis' but was swimming ==> Expected :true Actual :false
  4. https://linktr.ee/jeanneboyarsky Making this better (with Hamcrest) 9 import static org.hamcrest.MatcherAssert.assertThat;

    import static org.hamcrest.Matchers.*; … @Test void summerFunWithHamcrest() { assertThat(fun.getFavoriteActivity(), startsWith("swim")); assertThat(fun.getFavoriteActivity(), startsWith("tennis")); } java.lang.AssertionError: Expected: a string starting with "tennis" but: was "swimming" Expected :a string starting with "tennis" Actual :"swimming"
  5. https://linktr.ee/jeanneboyarsky Making this better (with Hamcrest) 10 import static org.hamcrest.MatcherAssert.assertThat;

    import static org.hamcrest.Matchers.*; … @Test void summerFunWithHamcrest() { assertThat(fun.getFavoriteActivity(), startsWith("swim")); assertThat(fun.getFavoriteActivity(), startsWith("tennis")); } java.lang.AssertionError: Expected: a string starting with "tennis" but: was "swimming" Expected :a string starting with "tennis" Actual :"swimming"
  6. https://linktr.ee/jeanneboyarsky Making this better (with AssertJ) 11 import static org.assertj.core.api.Assertions.assertThat;

    … @Test void summerFunWithAssertJ() { assertThat(fun.getFavoriteActivity()).startsWith("swim"); assertThat(fun.getFavoriteActivity()).startsWith("tennis"); } java.lang.AssertionError: Expecting actual: "swimming" to start with: "tennis"
  7. https://linktr.ee/jeanneboyarsky Adding a custom message (AssertJ) 12 @Test void summerFunMessageWithAssertJ()

    { assertThat(fun.getFavoriteActivity()) .as("I like swimming").startsWith("swim"); assertThat(fun.getFavoriteActivity()) .as("I like tennis").startsWith("tennis"); } java.lang.AssertionError: [I like tennis] Expecting actual: "swimming" to start with: "tennis" Warning: Placement of as() matters
  8. https://linktr.ee/jeanneboyarsky Multiple checks 13 // public List<String> getFavoriteActivities() { //

    return List.of("swimming", "sunbathing"); // } @Test void summerFunListWithHamcrest() { assertThat(fun.getFavoriteActivities(), hasItem("sunbathing")); assertThat(fun.getFavoriteActivities(), hasSize(2)); } @Test void summerFunListWithAssertJ() { assertThat(fun.getFavoriteActivities()) .contains(“sunbathing") .hasSize(2); }
  9. https://linktr.ee/jeanneboyarsky Which more readable? 14 @Test void summerFunListWithHamcrest() { assertThat(fun.getFavoriteActivities(),

    hasItem("sunbathing")); assertThat(fun.getFavoriteActivities(), hasSize(2)); } @Test void summerFunAllOfHamcrest() { assertThat(fun.getFavoriteActivities(), Matchers.<Collection<String>> allOf( hasSize(2), hasItem("sunbathing") )); }
  10. https://linktr.ee/jeanneboyarsky Checking Each Element 15 @Test void summerFunStartingCharacterWithHamcrest() { assertThat(fun.getFavoriteActivities(),

    everyItem(startsWith("s"))); } @Test void summerFunStartingCharacterWithAssertJ() { assertThat(fun.getFavoriteActivities()) .allSatisfy(w -> assertThat(w).startsWith("s")); }
  11. https://linktr.ee/jeanneboyarsky Sample Assertions 16 Hamcrest AssertJ Null nullValue() notNullValue() isNull()

    isNotNull() Equality is(o) not(o) equalTo() older version isEqualTo(o) isNotEqualTo(o) String startsWith(s) endsWith(s) equalToIgnoringCase(s) startsWith(s) endsWith(s) isEqualToIgnoringCase(s) Collection hasSize(n) hasItems(x) not(hasItem(x)) hasSize(n) contains(x) doesNotContain(x)
  12. https://linktr.ee/jeanneboyarsky Comparing 17 Hamcrest • Declarative • Older • Less

    updates • Multiple or combined assertions • Integration with JUnit 4 assertThat or standalone in JUnit 5 AssertJ • Fluent • Newer • More updates • Chaining assertions (faciliates autocomplete) • Can use with JUnit 4 or 5
  13. https://linktr.ee/jeanneboyarsky Testing Exceptions 19 var actual = assertThrows(ThunderstormException.class, () ->

    fun.weatherCheck()); assertThat(actual.getMessage().contains(“pool")); assertThrows(IllegalStateException.class, () -> fun.weatherCheck()); org.opentest4j.AssertionFailedError: Unexpected exception type thrown, Expected :class java.lang.IllegalStateException Actual :class com.jeanneboyarsky.example.SummerFun$ThunderstormException
  14. https://linktr.ee/jeanneboyarsky Soft Assertions 20 @Test void callingAssertAll() { var softly

    = new SoftAssertions(); softly.assertThat(“robot") .isEqualTo("izzy"); softly.assertThat(126) .isLessThanOrEqualTo(125); softly.assertAll();; } org.assertj.core.error. AssertJMultipleFailuresError: Multiple Failures (2 failures) -- failure 1 -- expected: "izzy" but was: "robot" at SoftAssertionsTest.calling… -- failure 2 -- Expecting actual: 126 to be less than or equal to: 125 at SoftAssertionsTest.calling…
  15. https://linktr.ee/jeanneboyarsky Custom Assertion 21 class CustomAssertions extends AbstractAssert<CustomAssertions, String> {

    public CustomAssertions(String actual) { super(actual, CustomAssertions.class); } public static CustomAssertions assertThat(String actual) { return new CustomAssertions(actual); } public CustomAssertions isTennis() { if (! "tennis".equals(actual)) failWithMessage("Must be tennis but was %s", actual); return this; } }
  16. https://linktr.ee/jeanneboyarsky More AssertJ Capabilities 23 • Recursive assertions • More

    types: • date • path • file • numbers • collections - ex: extract/filter data • etc
  17. https://linktr.ee/jeanneboyarsky Disable Until 25 @Test @DisabledUntil(date="2025-10-31", reason = "Need our

    partner service to be ready") void waitingForServiceAvailability() { … } timestamp = 2025-07-05T11:50:33.673759, DisabledUntil = This test is disabled until 2025-10-31. If executing it on this commit would fail, the build can't be reproduced after that date. The `date` 2025-10-31 is after the current date 2025-07-05
  18. https://linktr.ee/jeanneboyarsky Retrying a Test 26 @RetryingTest(10) void flakeyTest() { Random

    random = new Random(); if (random.nextInt(1, 100) < 75) { fail("too high"); } }
  19. https://linktr.ee/jeanneboyarsky System Properties 27 @Test @ClearSystemProperty(key = "JAVA_HOME") void clearProperty()

    { assertNull(System.getProperty("JAVA_HOME"), "path"); } @Test @SetSystemProperty( key = "JAVA_HOME", value = "c:/java/java21") void maskProperty() { assertEquals("c:/java/java21", System.getProperty("JAVA_HOME")); } Also @SetEnvironmentVariable
  20. https://linktr.ee/jeanneboyarsky More Pioneer Capabilities 29 • System in/out/err • Set

    detault time zone/locale • Disable selected parameterized tests • Expected to fail (fails if passes) • Parameterized tests with JSON • Temporary directories (also in JUnit) • etc
  21. https://linktr.ee/jeanneboyarsky Test Doubles 31 Term Description Dummy Passed in; never

    used Fake Some working implementation; simple Stub Contrived responses Spy Merge real and mock behavior Mock Preprogrammed with expectations
  22. https://linktr.ee/jeanneboyarsky Class to test 32 public interface ScoreService { int

    retrieveScore(int matchNumber); } public class Dashboard { private ScoreService service; public Dashboard(ScoreService service) { this.service = service; } public List<Integer> getScores(int maxMatch) { return IntStream.range(1, maxMatch + 1) .mapToObj(n -> service.retrieveScore(n)) .toList(); } }
  23. https://linktr.ee/jeanneboyarsky Mockito 33 @ExtendWith(MockitoExtension.class) public class DashboardTest { private Dashboard

    dashboard; @Mock private ScoreService scoreServiceMock; @BeforeEach void setUp() { dashboard = new Dashboard(scoreServiceMock); }
  24. https://linktr.ee/jeanneboyarsky Mockito (cont) 34 @Test void getScores() { when(scoreServiceMock.retrieveScore(1)).thenReturn(76); when(scoreServiceMock.retrieveScore(2)).thenReturn(91);

    List<Integer> expected = List.of(76, 91); List<Integer> actual = dashboard.getScores(2); assertEquals(expected, actual, "scores"); verify(scoreServiceMock, times(2)).retrieveScore(anyInt()); } }
  25. https://linktr.ee/jeanneboyarsky EasyMock 35 @ExtendWith(EasyMockExtension.class) public class DashboardTest { private Dashboard

    dashboard; @Mock private ScoreService scoreServiceMock; @BeforeEach void setUp() { dashboard = new Dashboard(scoreServiceMock); }
  26. https://linktr.ee/jeanneboyarsky EasyMock (cont) 36 @Test void getScores() { expect(scoreServiceMock.retrieveScore(1)).andReturn(76); expect(scoreServiceMock.retrieveScore(2)).andReturn(91);

    replay(scoreServiceMock); List<Integer> expected = List.of(76, 91); List<Integer> actual = dashboard.getScores(2); assertEquals(expected, actual, "scores"); verify(scoreServiceMock); } }
  27. https://linktr.ee/jeanneboyarsky More Mockito Capabilities 37 • Strict or lenient modes

    • Exceptions • Stubbing • Spies (monitor real objects) • Verify args, # of times API called • Mocking final classes, static methods • etc
  28. https://linktr.ee/jeanneboyarsky Creating driver 39 WebDriver driver = null; try {

    var options = new ChromeOptions(); options.addArguments("--headless=new"); driver = new ChromeDriver(options); … } finally { if (driver != null) driver.close(); }
  29. https://linktr.ee/jeanneboyarsky Reading HTML 40 driver.get("https://www.kcdc.info"); var element = driver.findElement( By.cssSelector(“a[href$='/speakers']"));

    assertEquals(“https://www.kcdc.info/speakers", element.getAttribute("href"), “url"); assertEquals(“Speakers", element.getAttribute("textContent").strip(), "display name");
  30. https://linktr.ee/jeanneboyarsky Dynamic load/longer way 41 driver.get("https://www.kcdc.info/speakers"); var wait = new

    WebDriverWait(driver, Duration.ofSeconds(60)); wait.until(ExpectedConditions.presenceOfElementLocated( By.tagName("h3"))); boolean match = driver.findElements(By.tagName("h3")) .stream() .anyMatch(e -> "Jeanne Boyarsky".equals(e.getText())); assertTrue(match, "Found Jeanne in speaker list");
  31. https://linktr.ee/jeanneboyarsky Locator types 42 • By.id • By.name • By.className

    • By.tagName • By.cssSelector • By.xpath • By.linkText • By.partialLinkText
  32. https://linktr.ee/jeanneboyarsky More Selenium Capabilities 43 • Many browser integrations •

    Automate clicking/forms • Can capture screenshots • Selenium IDE • Selenium Grid • etc
  33. https://linktr.ee/jeanneboyarsky Generating Tests 49 Technique Available In Generating class/stub methods

    Direct in IDE Test Me Generation IntelliJ AI written tests AI plugins
  34. https://linktr.ee/jeanneboyarsky Golden Master 52 • Regression test output doesn’t change

    • Save last good value • text block • file • If changes, decide if correct • Update golden master if so
  35. https://linktr.ee/jeanneboyarsky Behavior Driven Development 53 • Given a new conference

    attendee who wants to eat lunch • When the last morning session ends • Then there will be signage to food
  36. https://linktr.ee/jeanneboyarsky Mutation Testing 54 • High code coverage isn’t enough

    • Run tests many times with mutants • If a mutant is not detected, tests insufficient