Skip to main content
Sam Brannen
@sam_brannen
SpringOne 2021
Testing with
and
Copyright © 2021 VMware, Inc. or its affiliates.
JUnit
©2020 VMware, Inc. 2
This presentation may contain product features or functionality that are currently under
development.
This overview of new technology represents no commitment from VMware to deliver these
features in any generally available product.
Features are subject to change, and must not be included in contracts, purchase orders, or sales
agreements of any kind.
Technical feasibility and market demand will affect final delivery.
Pricing and packaging for any new features/functionality/technology discussed or presented, have
not been determined.
The information in this presentation is for informational purposes only and may not be incorporated into any contract. There is no
commitment or obligation to deliver any items presented herein.
Disclaimer
Sam Brannen
● Staff Software Engineer
● Java Developer for over 20 years
● Spring Framework Core Committer since 2007
● JUnit 5 Core Committer since October 2015
Cover w/ Image
Agenda
● JUnit 5.7
● JUnit 5.8
● Spring 5.3
● GraalVM
● Q&A
JUnit Jupiter Support in Spring
JUnit Jupiter and Spring are a great match for testing
● Spring Framework
● @ExtendWith(SpringExtension.class)
● @SpringJUnitConfig
● @SpringJUnitWebConfig
● Spring Boot
● @SpringBootTest
● @WebMvcTest, etc.
JUnit 5.7
Odds and Ends
https://junit.org/junit5/docs/5.7.2/release-notes/
● Numerous APIs promoted from experimental to stable
● Improvements to EngineTestKit
● Improvements to Assertions
● Improvements to @CsvFileSource and @CsvSource
● Custom disabledReason for all @Enabled* / @Disabled* annotations
Major Features in 5.7
● Java Flight Recorder support
● junit.jupiter.testmethod.order.default configuration parameter to set the
default MethodOrderer
● used unless @TestMethodOrder is present
● @EnabledIf and @DisabledIf: based on condition methods
● @Isolated: run the test class in isolation during parallel execution
● TypedArgumentConverter for converting one specific type to another
○ use with @ConvertWith in a @ParameterizedTest
○ enhancements and bug fixes in 5.8
JUnit 5.8
Major Features in JUnit Platform 1.8
● Declarative test suites via @Suite classes
● SuiteTestEngine in junit-platform-suite-engine module
● new annotations in junit-platform-suite-api module
■ @Suite, @ConfigurationParameter, @SelectUris, @SelectFile, etc.
● UniqueIdTrackingListener
○ TestExecutionListener that tracks the unique IDs of all tests
○ generates a file containing the unique IDs
○ can be used to rerun those tests 
■ for example, with GraalVM Native Build Tools
Example: Suites before 5.8 – Soon to be Deprecated
// Uses JUnit 4 to run JUnit 5
@RunWith(JUnitPlatform.class)
@SuiteDisplayName("Integration Tests")
@IncludeEngines("junit-jupiter")
@SelectPackages("com.example")
@IncludeTags("integration-test")
public class IntegrationTestSuite {
}
Example: Suites with JUnit 5.8
// Uses JUnit 5 to run JUnit 5
@Suite
@SuiteDisplayName("Integration Tests")
@IncludeEngines("junit-jupiter")
@SelectPackages("com.example")
@IncludeTags("integration-test")
public class IntegrationTestSuite {
}
Small Enhancements in JUnit 5.8
https://junit.org/junit5/docs/snapshot/release-notes/
● More fine-grained Java Flight Recorder (JFR) events
● plus support on Java 8 update 262 or higher
● assertThrowsExactly()
○ alternative to assertThrows()
● assertInstanceOf()
○ instead of assertTrue(obj instanceof X)
● @RegisterExtension fields may now be private
New in JUnit Jupiter 5.8
Test Class Execution Order
● ClassOrderer API analogous to the MethodOrderer API
○ ClassName
○ DisplayName
○ OrderAnnotation
○ Random
● Global configuration via junit.jupiter.testclass.order.default configuration
parameter for all test classes
● for example, to optimize the build
● Local configuration via @TestClassOrder for @Nested test classes
Example: @TestClassOrder
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
class OrderedNestedTests {
@Nested
@Order(1)
class PrimaryTests {
@Test
void test1() {}
}
@Nested
@Order(2)
class SecondaryTests {
@Test
void test2() {}
}
}
@TempDir – New Behavior
Due to popular demand from the community…
● @TempDir previously created a single temporary directory per context
● @TempDir can now be used to create multiple temporary directories
● JUnit now creates a separate temporary directory per @TempDir annotation
● Revert to the old behavior by setting the junit.jupiter.tempdir.scope
configuration parameter to per_context
@ExtendWith on Fields and Parameters
Improves programming model
● @RegisterExtension: register extensions via fields programmatically
○ nothing new
● @ExtendWith: can now register extensions via fields and parameters declaratively
● fields: static or instance
● parameters: constructor, lifecycle method, test method
● typically as a meta-annotation
● avoids the need to declare @ExtendWith at the class or method level
Example: RandomNumberExtension
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(RandomNumberExtension.class)
public @interface Random {
}
class RandomNumberExtension implements BeforeAllCallback,
BeforeEachCallback, ParameterResolver {
// implementation...
}
Example: @Random in Action
class RandomNumberTests {
@Random
private int randomNumber1;
RandomNumberTests(@Random int randomNumber2) {
}
@BeforeEach
void beforeEach(@Random int randomNumber3) {
}
@Test
void test(@Random int randomNumber4) {
}
}
Named API
● Named: container that associates a name with a given payload
○ The meaning of the payload depends on the context
○ Named.of() vs. Named.named()
● DynamicTests.stream() can consume Named input and will use each name-value pair
as the display name and value for each generated dynamic test
● In parameterized tests using @MethodSource or @ArgumentSource, arguments can
now have explicit names supplied via the Named API
○ explicit name used in display name instead of the argument value
Example: Named Dynamic Tests
@TestFactory
Stream<DynamicTest> dynamicTests() {
Stream<Named<String>> inputStream = Stream.of(
named("racecar is a palindrome", "racecar"),
named("radar is also a palindrome", "radar"),
named("mom also seems to be a palindrome", "mom"),
named("dad is yet another palindrome", "dad")
);
return DynamicTest.stream(inputStream,
text -> assertTrue(isPalindrome(text)));
}
AutoCloseable Arguments in Parameterized Tests
● In parameterized tests, arguments that implement AutoCloseable will now be
automatically closed after the test completes
● Allows for automatic cleanup of resources
○ closing a file
○ stopping a server
○ etc.
● Similar to the CloseableResource support in the ExtensionContext.Store
Spring Framework 5.3
New in Spring Framework 5.3 GA to 5.3.9
https://github.com/spring-projects/spring-framework/releases
● Test configuration is now discovered on enclosing classes for @Nested test classes
● ApplicationEvents abstraction for capturing application events published in the
ApplicationContext during a test
● Set spring.test.constructor.autowire.mode in junit-platform.properties
● Detection for @Autowired violations in JUnit Jupiter
● Improvements for file uploads and multipart support in MockMvc and
MockRestServiceServer
● Various enhancements in MockHttpServletRequest and MockHttpServletResponse
● Improved SQL script parsing regarding delimiters and comments
Example: @Nested tests before 5.3
@SpringJUnitConfig(TestConfig.class)
@ActiveProfiles("dev")
@Transactional
class DevTests {
@Nested
@SpringJUnitConfig(TestConfig.class)
@ActiveProfiles("dev")
@Transactional
class OrderTests { /* tests */ }
@Nested
@SpringJUnitConfig(PricingConfig.class)
class PricingTests { /* tests */ }
}
Example: @Nested tests after 5.3
@SpringJUnitConfig(TestConfig.class)
@ActiveProfiles("dev")
@Transactional
class DevTests {
@Nested
class OrderTests { /* tests */ }
@Nested
@NestedTestConfiguration(OVERRIDE)
@SpringJUnitConfig(PricingConfig.class)
class PricingTests { /* tests */ }
}
Example: ApplicationEvents
@SpringJUnitConfig(/* ... */)
@RecordApplicationEvents
class OrderServiceTests {
@Autowired OrderService orderService;
@Autowired ApplicationEvents events;
@Test
void submitOrder() {
// Invoke method in OrderService that publishes an event
orderService.submitOrder(new Order(/* ... */));
// Verify that 1 OrderSubmitted event was published
assertThat(events.stream(OrderSubmitted.class)).hasSize(1);
}
}
Coming in Spring Framework 5.3.10
Improving every step of the way…
● ExceptionCollector testing utility
● Soft assertions for MockMvc and WebTestClient
● Support for HtmlFileInput.setData() with HtmlUnit and MockMvc
● setDefaultCharacterEncoding() in MockHttpServletResponse
● Default character encoding for responses in MockMvc
Example: MockMvc without Soft Assertions
mockMvc.perform(get("/person/5").accept(APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Jane"));
Example: MockMvc with Soft Assertions
mockMvc.perform(get("/person/5").accept(APPLICATION_JSON))
.andExpectAll(
status().isOk(),
jsonPath("$.name").value("Jane")
);
Example: WebTestClient without Soft Assertions
webTestClient.get().uri("/test").exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("hello");
Example: WebTestClient with Soft Assertions
webTestClient.get().uri("/test").exchange()
.expectAll(
spec -> spec.expectStatus().isOk(),
spec -> spec.expectBody(String.class).isEqualTo("hello")
);
Example: MockMvc default response character encoding
MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = webAppContextSetup(wac)
.defaultResponseCharacterEncoding(StandardCharsets.UTF_8)
.build();
}
@Test
void getPerson() throws Exception {
this.mockMvc.perform(get("/person/1").characterEncoding(UTF_8))
.andExpect(status().isOk())
.andExpect(content().encoding(UTF_8));
}
GraalVM Native Build Tools
GraalVM Native Build Tools
“The Native Build Tools project provides plugins for different build
tools to add support for building and testing native applications
written in Java (or any other language compiled to JVM bytecode)”
● Collaboration between the GraalVM team, the Spring team, and recently the
Micronaut team as well
● Build tools
● Gradle plugin
● Maven plugin
● Compiling native images
● Testing within a native image using the JUnit Platform
Why test within a native image?
● Java is “write once; run anywhere”.
● … but a native image is not your app running on the JVM.
● You could just rely on your normal JVM-based test suite.
● … because your app is already tested – right?
● You could test your native app from the outside – for example, via HTTP endpoints.
○ … but do you have HTTP endpoints for every feature of your app?
● Best practice:
○ Write your JVM tests like you normally do.
○ Run the same tests (or a subset) within a native image.
How can a Native Build Tools plugin help?
● Runs your tests in the JVM first, using the UniqueIdTrackingListener
○ Tracks the tests you want to run in the native image
○ Necessary since classpath scanning doesn’t work in a native image
● If your tests or the code you’re testing requires reflection, you might need to run your
JVM tests with the GraalVM agent
● Compiles your app and tests into a native image using the JUnitPlatformFeature
(GraalVM Feature)
● Runs your tests within the native image using the NativeImageJUnitLauncher
○ Launches the JUnit Platform and selects your tests based on the output of the
UniqueIdTrackingListener
● Outputs results to the console and XML test reports
How to use Native Build Tools
● Follow the instructions for configuring your Gradle or Maven build
○ https://graalvm.github.io/native-build-tools/
● Or use the Spring Native support configured for your automatically
○ https://start.spring.io
● Gradle:
○ gradle nativeTest
○ gradle –Pagent test and gradle –Pagent nativeTest
● Maven:
○ mvn –Dskip -Pnative test
Things to keep in mind
● Not everything works in a native image
○ Classpath scanning
○ Dynamic class creation
○ Mockito and various mocking libraries
● You might need to exclude certain tests within a native image
○ For example, via tags or a custom ExecutionCondition in JUnit Jupiter
● Building a native image can take a long time
○ Probably not something you want to do multiple times a day
○ A dedicated CI pipeline might be a better choice
Q&A
Thank you
Twitter: @sam_brannen
Slack: #session-testing-with-junit-5-spring-and-native-images
© 2021 Spring. A VMware-backed project.
and JUnit