org.junit.jupiter
junit-jupiter-api
5.0.0-M3
Nicolai Parlog
JUnit 5 is work in progress!
This is based on Milestone 3
(released 30th of November 2016).
Give feedback!
Tools & Setup |
Basics |
Extensions |
Add this:
org.junit.jupiter
junit-jupiter-api
5.0.0-M3
Have fun!
individual classes:
@RunWith(JUnitPlatform.class)
public class JUnit5Test { ... }
all classes:
@RunWith(JUnitPlatform.class)
@SelectPackages({ "my.test.package" })
public class JUnit5TestSuite { }
JUnit 5 team provides rudimentary
Gradle plugin and Maven Surefire provider
(see user guide for details)
There is a console launcher:
# run all tests
junit-platform-console \
-cp ${path_to_compiled_test_classes} \
--scan-class-path
# run a specific test
junit-platform-console \
-cp ${path_to_compiled_test_classes} \
--select-class \
${fully_qualified_test_class_name}
you can start writing tests right away
only IntelliJ has native support
running with JUnit 4 is a good compromise
(Read about the setup details.)
Tools & Setup |
Basics |
Extensions |
class JUnit5Test {
@Test
void someTest() {
assertTrue(true);
}
}
⇝ Package visibility suffices!
@BeforeAll
static void beforeAll() { ... }
@BeforeEach
void beforeEach() { ... }
@AfterEach
void afterEach() { ... }
@AfterAll
static void afterAll() { ... }
⇝ Lifecycle annotations have new names.
@Test
@Disabled("Y U No Pass?!")
void failingTest() {
assertTrue(false);
}
⇝ @Ignored
is now @Disabled
.
@Test
@DisabledOnFriday
void failingTest() {
assertTrue(false);
}
⇝ Convenient Extensibility.
But how?
@Test
void someTest() {
...
assertEquals(
expected,
actual,
"Should be equal.");
}
⇝ Failure message comes last.
@Test
void someTest() {
...
assertEquals(
expected,
actual,
() -> "Should " + "be " + "equal.");
}
⇝ Failure message can be created lazily.
@Test
void assertAllProperties() {
Address ad = new Address(
"City", "Street", "42");
assertAll("address",
() -> assertEquals("C", ad.city),
() -> assertEquals("Str", ad.street),
() -> assertEquals("63", ad.number)
);
}
⇝ assertAll
gathers results from multiple assertions
Output if assertAll
fails:
org.opentest4j.MultipleFailuresError:
address (3 failures)
expected: <C> but was: <City>
expected: <Str> but was: <Street>
expected: <63> but was: (42)
void methodUnderTest() {
throw new IllegalStateException();
}
@Test
void assertExceptions() {
Exception ex = assertThrows(
Exception.class,
this::methodUnderTest);
assertEquals("Msg", ex.getMessage());
}
⇝ assertThrows
to assert
exception type and other properties
class CountTest {
// lifecycle and tests
@Nested
class CountGreaterZero {
// lifecycle and tests
@Nested
class CountMuchGreaterZero {
// lifecycle and tests
}
}
}
⇝ @Nested
to organize tests in inner classes
@DisplayName("A count")
class CountTest {
@Nested
@DisplayName("when greater zero")
class CountGreaterZero {
@Test
@DisplayName("is positive")
void isPositive() { ... }
}
}
⇝ @DisplayName
to show a nice name
The effects of @Nested
and @DisplayName
:
@Test
void someTest(MyServer server) {
// do something with `server`
}
⇝ Test has parameters!
But where do they come from?
lifecycle works much like before
many details were improved
@Nested
and @DisplayName
make a nice couple
parameter injection
no lambdas (so far)
Tools & Setup |
Basics |
Extensions |
Manage a test’s full lifecycle.
@RunWith(MockitoJUnitRunner.class)
public class MyTest { ... }
very flexible
heavyweight
exclusive
Execute code before and after statements.
public class MyTest {
@Rule
public MockitoRule rule =
MockitoJUnit.rule();
}
added in 4.7
lightweight
limited to before/after behavior
Extension model is not optimal:
two competing mechanisms
each with limitations
but with considerable overlap
composition can cause problems
From JUnit 5’s Core Principles:
Prefer extension points over features
Quite literally,
JUnit 5 has Extension Points
Test Instance Post Processor
BeforeAll Callback
Test and Container Execution Condition
BeforeEach Callback
Parameter Resolution
Before Test Execution
After Test Execution
Exception Handling
AfterEach Callback
AfterAll Callback
one interface for each extension point
method arguments capture context
public interface BeforeEachCallback
extends Extension {
void beforeEach(
TestExtensionContext context);
}
an extension can use multiple points
to implement its feature
We want to benchmark our tests!
for each test method
write the elapsed time to console
How?
before test execution: store test launch time
after test execution: print elapsed time
public class BenchmarkExtension implements
BeforeTestExecutionCallback,
AfterTestExecutionCallback {
private long launchTime;
// ...
}
@Override
public void beforeTestExecution(
TestExtensionContext context) {
launchTime = currentTimeMillis();
}
@Override
public void afterTestExecution(
TestExtensionContext context) {
printf("Test '%s' took %d ms.%n",
context.getDisplayName(),
currentTimeMillis() - launchTime);
}
Remember This?
@Test
@DisabledOnFriday
void failingTest() {
assertTrue(false);
}
Let’s see how it works!
public class DisabledOnFridayCondition
implements TestExecutionCondition {
@Override
public ConditionEval.Result evaluate(
TestExtensionCtx. context) {
if (isFriday())
return disabled("Weekend!");
else
return enabled("Fix it!");
}
}
What about parameter injection?
@Test
void someTest(MyServer server) {
// do something with `server`
}
public class MyServerParameterResolver
implements ParameterResolver {
@Override
public boolean supports(
ParameterContext p, ...) {
return MyServer.class
== p.getParameter().getType();
}
@Override
public Object resolve( ... ) {
return new MyServer();
}
}
Quick look at ExtensionContext
:
// every node has its own context
Optional<ExtensionContext> getParent();
// some node-related info
String getUniqueId();
String getDisplayName();
Set<String> getTags();
// don't use System.out !
void publishReportEntry(
Map<String, String> map);
Quick look at ExtensionContext
:
// to reflect over the test class/method
Optional<AnnotatedElement> getElement();
Optional<Class<?>> getTestClass();
Optional<Method> getTestMethod();
// use the store to safe extension state
// (extensions should be stateless;
// did I mention that?)
Store getStore();
Store getStore(Namespace namespace);
JUnit makes no promises regarding
extension instance lifecycle!
⇝ Extensions must be stateless!
Use the Store
, Luke!
namespaced
hierarchical
key-value
Store is accessed via ExtensionContext
given a Namespace
// forwards with a default namespace
Store getStore();
Store getStore(Namespace namespace);
keeps extensions from stepping
on each other’s toes
could allow deliberate communication!
Reads from the store forward to other stores:
method store ⇝ class store
nested class store ⇝ surrounding class store
Writes always go to the called store.
The store is essentially a map:
Object getObject(Object key);
Object getOrComputeIfAbsent(
K key, Function creator);
void put(Object key, Object value)
Object remove(Object key)
Overloads with type tokens exist.
void storeNowAsLaunchTime(
ExtensionContext context) {
long now = currentTimeMillis();
context.getStore(NAMESPACE)
.put(KEY, now);
}
long loadLaunchTime(
ExtensionContext context) {
return context.getStore(NAMESPACE)
.get(KEY, long.class);
}
How do we apply extensions?
@ExtendWith(DisabledOnFridayCondition.class)
class JUnit5Test {
...
}
That’s technical and verbose… :(
Meta-annotations to the rescue!
JUnit 5’s annotations are meta-annotations
JUnit 5 checks recursively for annotations
⇝ We can create our own annotations!
@ExtendWith(DisabledOnFridayCondition.class)
public @interface DisabledOnFriday { }
@Test
@Tag("integration")
@ExtendWith(BenchmarkExtension.class)
@ExtendWith(MyServerParameterResolver.class)
public @interface IntegrationTest { }
@IntegrationTest
@DisabledOnFriday
void testLogin(MyServer server) { ... }
flexibility because of many extension points
powerful due to extension context
need to be stateless!
extensions compose well
customizable due to meta-annotations
you can hire me
since 2016: editor of sitepoint.com/java
2014-2016: Java developer at Disy
2011-2014: Java developer at Fraunhofer ISI
until 2010: CS and Math at TU Dortmund
bubbles: Keith Williamson (CC-BY 2.0)
architecture diagrams:
Nicolai Parlog
(CC-BY-NC 4.0)
question-mark: Milos Milosevic (CC-BY 2.0)