Scott Leberknight
8/2/2018
JUnit
“…the next generation of JUnit…”
“…for Java 8 and beyond…”
“…enabling many different styles of testing”
Key Takeaways…
Next gen test framework…
New programming & extension model
for developers
Stable platform for tool providers
Migration path from earlier versions
Platform + Jupiter + Vintage
JUnit 5 =
JUnit Platform
Launcher for test frameworks on JVM
Defines TestEngine API
Console Launcher
Maven, Gradle plugins
JUnit Jupiter
New programming model
Standard assertions
Extension model
(replaces @Rules)
TestEngine for running Jupiter tests
JUnit Vintage
TestEngine to run
JUnit 3 & 4 tests
Eases migration
to Jupiter…
Dependencies
Separate dependency groups…
org.junit.platform
org.junit.jupiter
org.junit.vintage
junit-jupiter-api
junit-jupiter-engine
junit-jupiter-migrationsupport
junit-vintage-engine
…and more
common dependencies…
Writing tests
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class FirstJupiterTest {
@Test
void theAnswer() {
assertEquals(42, 22 + 20);
}
}
Jupiter Basics
No need to be public
Annotation-based (like JUnit 4)
Assertions & Assumptions
@Test
@DisplayName("All Your Base Are Belong To Us™")
void allYourBase() {
assertAll(
() -> assertTrue(true),
() -> assertEquals("42", "4" + "2"),
() -> assertTimeout(ofSeconds(1), () -> 42 * 42)
);
}
@Test
@DisplayName("To ∞ & beyond...")
void toInfinity() {
Integer result = assertDoesNotThrow(() ->
ThreadLocalRandom.current().nextInt(42));
assertTrue(result < 42);
}
@Test
@Test indicates a test method
Test methods cannot be private or static
Test methods can declare parameters
Jupiter supports meta-annotations
@Slf4j
class FirstParameterTest {
@BeforeEach
void setUp(TestInfo info) {
LOG.info("Executing test: {} tagged with {}",
info.getDisplayName(), info.getTags());
}
@Test
@Tag("fast")
void multiply() {
assertEquals(42, 21 * 2);
}
}
( @Slf4j is a Lombok annotation that generates an SLF4J Logger )
Jupiter Annotations
Reside in org.junit.jupiter.api
Some common ones:
@BeforeEach, @AfterEach
@BeforeAll, @AfterAll
@DisplayName
@Tag
@Disabled
Jupiter Annotations
And some more exotic ones…
@Nested
@TestInstance
@TestFactory
@TestTemplate
@ParameterizedTest
@RepeatedTest
Meta-Annotations
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
@interface Fast {}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Test
@Fast
public @interface FastTest {}
@FastTest
void lightning() {
assertFalse(false);
}
@TestInstance
Controls lifecycle of test instances
Per method is the default;
creates new instance for every test
Per class re-uses the same
instance for every test
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Slf4j
class PerClassTestLifecycleTest {
private int sum = 0;
@Test
void add5() { sum += 5; }
@Test
void add10() { sum += 10; }
@AfterEach
void tearDown() {
LOG.info("The current sum is: {}", sum);
}
}
@TestInstance
In general, stick with the default
PER_METHOD lifecycle…
…and become aware of
differences (pitfalls?) in behavior
when using PER_CLASS
@Nested
Allows arbitrary levels of nesting
Group common sets of tests
Cleanly separate setup/teardown code
@DisplayName("An ArrayList")
class NestedExampleTest {
private ArrayList<String> list;
@Nested
@DisplayName(“when new”)
class WhenNew {
@BeforeEach void setUp() { list = new ArrayList<>(); }
@Test @DisplayName(“is empty”) void isEmpty() { ... }
@Nested
@DisplayName(“after adding an element”)
class AfterAddElement {
@BeforeEach void setUp() { list.add(“an item”); }
@Test @DisplayName(“is no longer empty”)
void isNotEmpty() { ... }
}
}
}
Nested test output
@ParameterizedTest
Execute tests multiple times with
different arguments
Requires a source of values
Currently an experimental feature!
class ParametersTest {
@ParameterizedTest
@ValueSource(ints = {2, 4, 6, 8, 10})
void evens(int value) {
assertEquals(0, value % 2,
() -> String.format("Expected %d mod 2 == 0", value));
}
@ParameterizedTest
@MethodSource("supplyOdds")
void odds(int value) {
assertEquals(1, value % 2);
}
private static Stream<Integer> supplyOdds() {
return Stream.of(1, 3, 5, 7, 9, 11);
}
}
Other value sources
Enums
CSV strings and files
Using ArgumentsProvider API
Dependency Injection
Constructors & methods can have parameters!
DIY using ParameterResolver API
Several built-in resolvers
(TestInfo, RepetitionInfo, and TestReporterParameterResolver)
@RepeatedTest(value = 10)
void repeatedTestWithInfo(RepetitionInfo repetitionInfo) {
assertTrue(repetitionInfo.getCurrentRepetition() <=
repetitionInfo.getTotalRepetitions());
assertEquals(10, repetitionInfo.getTotalRepetitions());
}
More…
Disabling & conditional execution
Tagging & filtering
Repeated tests
Assumptions
Test templates & dynamic tests
A word on Assertions
Jupiter provides the basics…
…and several more useful ones
But consider using 3rd party
assertion frameworks like AssertJ,
Hamcrest, or Google’s Truth
(asssertAll, assertTimeout, assertThrows, etc.)
Extension
Model
Extension API
Replaces JUnit 4 Runner, @Rule, & @ClassRule
Extension “marker” interface
Lifecycle callbacks define extension points
Constructor & method parameters
Registering extensions
Declarative via @ExtendWith
Programmatically (code + @RegisterExtension)
Java’s ServiceLoader mechanism
@ExtendWith
Apply to test classes & methods
Register multiple extensions
@ExtendWith(TempDirectory.class)
class FileExporterTest {
private Path tempDirectory;
@BeforeEach
void setUp(@TempDirectory.Root Path tempDir) {
this.tempDirectory = tempDir;
}
// ...
}
class ReportGeneratorTest {
@ExtendWith(TempDirectory.class)
@Test
void excelReport(@TempDirectory.Root Path tempDir) {
// ...
}
@Test
void clipboardReport() { ... }
@ExtendWith(TempDirectory.class)
@Test
void pdfReport(@TempDirectory.Root Path tempDir) {
// ...
}
}
@RegisterExtension
Programmatically construct extensions
Can register multiple extensions
class RandomThingsTest {
@RegisterExtension
final SoftAssertionsExtension softly =
new SoftAssertionsExtension();
@Test void addition() {
softly.assertThat(2 + 2).isEqualTo(4);
// ...
}
@Test void substrings() {
String str = "Quick brown fox jumped over the lazy dog";
softly.assertThat(str).contains("Quick");
softly.assertThat(str).contains(“brown");
softly.assertThat(str).contains(“lazy");
softly.assertThat(str).contains(“dog");
}
}
Extension lifecycle
BeforeAllCallback
BeforeEachCallback
BeforeTestExecutionCallback
AfterTestExecutionCallback
AfterEachCallback
AfterAllCallback
TestExecutionExceptionHandlerTest happens
here
(*)
A simple extension…
@Slf4j
public class SoftAssertionsExtension
extends SoftAssertions implements AfterEachCallback {
public SoftAssertionsExtension() {
LOG.trace("Constructed new instance");
}
public void afterEach(ExtensionContext context) {
LOG.trace("Asserting all soft assertions”);
assertAll();
}
}
SoftAssertions is an AssertJ class
More extension features
Automatic registration via ServiceLoader
Keeping state via ExtensionContext.Store
ParameterResolver for resolving parameters
Test instance post-processing
Conditional test execution
(disabled by default)
Migrating from JUnit 4…
No @Rules
Without any rules, it’s pretty easy!
…mostly changing imports
…and some annotations, e.g.
@Before to @BeforeEach
With @Rules
With existing rules, it depends…
Converting most rules is fairly easy
Many frameworks already have
JUnit 5 extensions
(e.g. Dropwizard, Spring, Mockito, …)
Migration Support
Must use @EnableRuleMigrationSupport
Includes support for:
ExternalResource
ExpectedException
Verifier (including ErrorCollector)
(*)
(*) this probably covers a lot of existing rules
APIs
Support API evolution over time
Mark public interface with @API status
Uses @APIGuardian for status, e.g.
STABLE, INTERNAL, EXPERIMENTAL
Wrap up
New programming & extension model
Allows migration over time and
keeping JUnit 4 & 5 in same project
Separates platform from test engines
Recommend use 3rd-party assertion library
sample code
available at:
https://github.com/sleberknight/junit5-presentation-code
References
JUnit 5 on GitHub
https://github.com/junit-team/junit5
@API Guardian
https://github.com/apiguardian-team/apiguardian
JUnit 5 web site
https://junit.org/junit5/
JUnit 5 User Guide
https://junit.org/junit5/docs/current/user-guide/
Dom Pérignon
https://www.flickr.com/photos/tromal/6989654843
https://creativecommons.org/licenses/by/2.0/
No changes made
Photos & Images
(All other images were purchased from iStock)
Jupiter Beer
https://www.flickr.com/photos/quinnanya/30680378305/
https://creativecommons.org/licenses/by-sa/2.0/
No changes made
My Info
sleberknight at
fortitudetec.com
www.fortitudetec.com
@sleberknight
scott.leberknight at
gmail

JUnit 5