Take a Deep Dive into JUnit 5 with core committer Sam Brannen!
Over the last decade a lot has happened in the world of Java and testing, but JUnit 4 hasn't kept up. Now JUnit 5 is here to help shape the future of testing on the JVM with a focus on Java 8 language features, extensibility, and a modern programming API for testing in Java. Moreover, JUnit isn't just a Java testing framework anymore. Third parties are already developing test engines for Scala, Groovy, Kotlin, etc. that run on the new JUnit Platform.
This session starts off with an overview of the inspiration for & architecture of JUnit 5, from launchers to test engines. Sam will then take the audience on a live coding tour, highlighting support for tagging, custom display names, dependency injection, repeated tests, parameterized tests, conditional test execution, lambda expressions for assertions, assumptions, & dynamic tests, and implementing tests via interface default methods (a.k.a., testing traits).
Next, Sam will present the new extension model in JUnit Jupiter, discussing how to author and register extensions for conditional tests, parameter resolution (a.k.a., dependency injection), lifecycle callbacks, & more.
To round off the session, Sam will quickly showcase the new JUnit Jupiter support in Spring Framework 5.0.
2. @sam_brannen#JavaOne #JUnit5
Sam Brannen
• Spring and Java Consultant
• Trainer, Coach, …
• Hardcore developer at heart
• Java Developer for about 20 years
• Spring Framework Core Committer since 2007
• JUnit 5 Core Committer since October 2015
3. @sam_brannen#JavaOne #JUnit5
Experts in Spring and Enterprise Java
Areas of expertise
• Spring *
• JUnit 5
• Java EE
• Software Architecture
• Code Reviews
Where you find us
• Zurich, Switzerland
• @swiftmind
• http://www.swiftmind.com
7. @sam_brannen#JavaOne #JUnit5
Impetus for Change
• JUnit 4.0 was released a decade ago
• a lot has changed since then…
• testing needs have matured
• expectations have grown
• Modularity à big ball of mud (i.e., only THE junit.jar)
• Test discovery and execution à tightly coupled
• Extensibility à lot of room for improvement
• Let’s not forget Java 8 and Java 9
8. @sam_brannen#JavaOne #JUnit5
JUnit 4 Runner API
• Very powerful
• In fact, it can do anything
• But… you can’t combine Runners
• Parameterized + SpringRunner à no way
9. @sam_brannen#JavaOne #JUnit5
JUnit 4… Rules… are meant to be broken
• JUnit 4.7: MethodRule à @Rule
• JUnit 4.9: TestRule à @Rule / @ClassRule
• Great for simple use cases
• Can even be combined
• But… a single rule can’t be used for method-level and class-level callbacks
• Plus… zero support for instance-level callbacks
• Case in point: SpringClassRule / SpringMethodRule
10. @sam_brannen#JavaOne #JUnit5
JUnit Lambda – Crowdfunding Campaign
• Initiated by Johannes Link and Marc Philipp
• Later joined by Matthias Merdes, Stefan Bechtold, &
Sam Brannen
• Ran from July to October 2015
• Raised 53,937 Euros from 474 individuals and
companies
• 4 companies donated 6 weeks of developer time
14. @sam_brannen#JavaOne #JUnit5
Roadmap
• Prototype à December 2nd, 2015
• 5.0.0-ALPHA à February 1st, 2016
• 6 Milestones à July 2016 – July 2017
• 2-3 Release Candidates à July 2017 – August 2017
• 5.0.0 GA à September 10th, 2017 ✅
• 5.0.1 à Today! (October 3rd, 2017) ✅
15. @sam_brannen#JavaOne #JUnit5
JUnit 5 – in a Nutshell
• Modular
• Extensible
• Modern
• Forward and backward compatible
• JUnit Platform supports JUnit 3.8, JUnit 4, and JUnit 5
• New testing frameworks can be run with JUnit 4 infrastructure
o @RunWith(JUnitPlatform.class)
16. @sam_brannen#JavaOne #JUnit5
JUnit 5 – Java Versions
• Java 8
• baseline
• but can be used to test application code compiled against previous JDK versions
• Java 9
• #WorksFineOnJDK9
• every artifact has a stable AUTOMATIC-MODULE-NAME
• module-path scanning support coming in 5.1
17. @sam_brannen#JavaOne #JUnit5
JUnit 5 = Platform + Jupiter + Vintage
• JUnit Platform 1.0.0
• Foundation for launching testing frameworks on the JVM
• Launcher and TestEngine APIs
• ConsoleLauncher, Gradle plugin, Maven Surefire provider
• JUnit Jupiter 5.0.0
• New programming model and extension model for JUnit 5
• JUnit Vintage 4.12.0
• TestEngine for running JUnit 3 and JUnit 4 based tests
Revolutionary
Evolutionary
Necessary
18. @sam_brannen#JavaOne #JUnit5
Launcher API
• Used by IDEs and build tools to launch the framework
• Central API for discovering and executing tests via one or more engines
• LauncherDiscoveryRequest
• selectors and filters
• Feedback provided via the TestExecutionListener API
• ConsoleLauncher for command-line support
19. @sam_brannen#JavaOne #JUnit5
TestEngine API
• Test engine discovers and executes tests
• for a particular programming model
• Automatic registration via Java’s ServiceLoader mechanism
• JupiterTestEngine
• VintageTestEngine
• Implement your own…
23. @sam_brannen#JavaOne #JUnit5
IDEs and Build Tools
• IntelliJ: ✅ IDEA 2016.2+
• Eclipse: ✅ Eclipse Oxygen 4.7.1a (to be released next week)
• NetBeans: 🙉🙈🙊
• Gradle: interim solution from JUnit Team
• to be taken over by Gradle team (end of 2017)
• Android JUnit 5: third-party Android support
• Maven: interim solution from JUnit Team
• being taken over by Maven Surefire team
See user guide and
sample apps for examples
24. @sam_brannen#JavaOne #JUnit5
JUnit Jupiter – Extension Model
• Extension
• marker interface
• org.junit.jupiter.api.extension
• package containing all extension APIs
• implement as many as you like
• @ExtendWith(...)
• used to register one or more extensions
• interface, class, or method level
o or as a meta-annotation
28. @sam_brannen#JavaOne #JUnit5
JUnit 4 Rule Migration Support
• @EnableRuleMigrationSupport
o located in experimental junit-jupiter-migrationsupport module
o registers 3 extensions for JUnit Jupiter
• ExternalResourceSupport
o TemporaryFolder, etc.
• VerifierSupport
o ErrorCollector, etc.
• ExpectedExceptionSupport
o ExpectedException
o minor bug in 5.0.0; fixed in 5.0.1
29. @sam_brannen#JavaOne #JUnit5
Assertions
org.junit.jupiter.api.Assertions
• Limited set of core assertions
• assertEquals(), assertNotNull(), etc.
• assertThrows() – λ
• assertTimeout() and assertTimeoutPreemptively() – λ
• assertAll() – λ
• Supplier<String> à λ for lazy failure message evaluation
• message is now the last parameter
• For more power, use AssertJ, Hamcrest, etc.
35. @sam_brannen#JavaOne #JUnit5
Test Names
• Names default to test class or test method names
• characters limited based on Java syntax
• Custom display names à @DisplayName
• Can contain spaces, special chars, and even emoji 😱
36. @sam_brannen#JavaOne #JUnit5
Dependency Injection
• Extension Model meets Programming Model
• ParameterResolver extension API
• resolves parameters for constructors or method
o not just @Test and lifecycle methods
• can register multiple simultaneously
• only one wins
• Use cases
• server URL, DataSource, Spring ApplicationContext, etc.
37. @sam_brannen#JavaOne #JUnit5
TestInfo
• TestInfo: inject into constructor, @Test, @BeforeEach, etc.
• access display name, tags, class, method
• TestInfoParameterResolver
• eating our own dog food ;-)
• See also:
• RepetitionInfo for @RepeatedTest
• TestReporter
• MockitoExtension
• SpringExtension
39. @sam_brannen#JavaOne #JUnit5
Conditional Test Execution
• Extension Model meets Programming Model
• ExecutionCondition
• @Disabled
• DisabledCondition
• eating our own dog food ;-)
• Deactivate via Launcher, system property, or junit-platform.properties file
• junit.conditions.deactivate = org.junit.*
Game Changer
40. @sam_brannen#JavaOne #JUnit5
Interface Default Methods
• Introduces the concept of a test interface
• Enables multiple inheritance in tests
• a.k.a., testing traits
• @BeforeAll / @AfterAll
• if using @TestInstance(Lifecyle.PER_CLASS)
• @BeforeEach / @AfterEach
• @Test / @RepeatedTest / @ParameterizedTest / @TestFactory
• @Tag
• @ExtendWith
• See StringTests and TestInterfaceDemo examples in user guide
42. @sam_brannen#JavaOne #JUnit5
Nested Test Classes
• Enables logical, hierarchical grouping of test classes
• with shared initialization and state from outer classes
• Declare @Nested on non-static nested classes
• i.e., inner classes
• You can even combine nested classes and test interfaces
• See TestingAStack example in user guide and Bowling Game Kata by Tim Riemer
44. @sam_brannen#JavaOne #JUnit5
Repeated Tests
• Annotate a method with @RepeatedTest instead of @Test
o and specify the number of repetitions
• Optionally have the RepetitionInfo injected as a method parameter
• Optionally override the display name
45. @sam_brannen#JavaOne #JUnit5
@RepeatedTest in Action
@RepeatedTest(5)
void repeatedTest(RepetitionInfo repetitionInfo) {
assertEquals(5, repetitionInfo.getTotalRepetitions());
}
@RepeatedTest(
value = 5,
name = "Wiederholung {currentRepetition} von {totalRepetitions}”
)
void repeatedTestInGerman() {
// ...
}
46. @sam_brannen#JavaOne #JUnit5
Parameterized Tests (junit-jupiter-params)
• Annotate a method with @ParameterizedTest instead of @Test
o and specify the source of the arguments
o optionally override the display name
• Sources
o @ValueSource: String, int, long, double
o @EnumSource
o @MethodSource
o @CsvSource & @CsvFileSource
o @ArgumentsSource & custom ArgumentsProvider
47. @sam_brannen#JavaOne #JUnit5
Argument Conversion
• Implicit conversion
o Primitive types and their wrappers
o Enums
o java.time types (JSR-310)
• Explicit conversion
o @ConvertWith and custom ArgumentConverter
o @JavaTimeConversionPattern built-in support for JSR-310
48. @sam_brannen#JavaOne #JUnit5
@ParameterizedTest in Action
@ParameterizedTest
@ValueSource(strings = {
"mom",
"dad",
"radar",
"racecar",
"able was I ere I saw elba"
})
void palindromes(String candidate) {
assertTrue(isPalindrome(candidate));
}
50. @sam_brannen#JavaOne #JUnit5
Dynamic Tests
• Conventional tests are static (i.e., known at compile time)
• @Test
• A DynamicTest is registered at runtime – λ
• as lambda expression in a stream, collection, etc.
• by a method annotated with @TestFactory
• Can also register a DynamicContainer for dynamic nesting
• Somewhat analogous to parameterized tests
• just more… dynamic 🤓
51. @sam_brannen#JavaOne #JUnit5
Dynamic Tests in Action
@TestFactory
Stream<DynamicTest> dynamicTestsFromIntStream() {
// Generates tests for the first 10 even integers.
return IntStream.iterate(0, n -> n + 2)
.limit(10)
.mapToObj(n ->
dynamicTest("test" + n,
() -> assertTrue(n % 2 == 0)));
}
55. @sam_brannen#JavaOne #JUnit5
Spring Support for JUnit Jupiter
• Fully integrated in Spring Framework 5.0!
• released last week 😎
• Supports all Core Spring TestContext Framework features
• Constructor and method injection via @Autowired, @Qualifier, @Value
• Conditional test execution via SpEL expressions
• ApplicationContext configuration annotations
• Also works with Spring Framework 4.3
https://github.com/sbrannen/spring-test-junit5
61. @sam_brannen#JavaOne #JUnit5
JUnit 5 Resources
Project Homepage à http://junit.org/junit5
User Guide à http://junit.org/junit5/docs/current/user-guide
Javadoc à http://junit.org/junit5/docs/current/api
GitHub à https://github.com/junit-team
Gitter à https://gitter.im/junit-team/junit5
Stack Overflow à http://stackoverflow.com/tags/junit5
62. @sam_brannen#JavaOne #JUnit5
Spring Resources
Spring Framework à http://projects.spring.io/spring-framework
Spring Guides à http://spring.io/guides
Spring JIRA à https://jira.spring.io
Spring on GitHub à https://github.com/spring-projects/spring-framework
Stack Overflow à spring, spring-test, spring-mvc, spring-boot, …
63. @sam_brannen#JavaOne #JUnit5
Additional JUnit 5 Talks @ JavaOne 2017
JUnit 5 BOF
Tuesday, Oct 03, 6:45 p.m. - 7:30 p.m. | Moscone West - Room 2016
JUnit5: Features, Architecture, and Extensibility
Wednesday, Oct 04, 2:45 p.m. - 3:30 p.m. | Moscone West - Room 2009