@jeanneboyarsky 1
Intro to Testing with JUnit 5
Jeanne Boyarsky
Sept 17, 2021
KCDC
speakerdeck.com/boyarsky
Slide 2
Slide 2 text
@jeanneboyarsky 2
Change
this report to add a field?
No problem! Wait. What is this
supposed to do?
Slide 3
Slide 3 text
@jeanneboyarsky 3
This
is taking so long. Ok got it.
My customers will be happy
now.
Slide 4
Slide 4 text
@jeanneboyarsky 4
The
numbers on the report are
wrong. How could you let this
happen!!!?
Slide 5
Slide 5 text
@jeanneboyarsky 5
Uh oh.
I wish I had used JUnit!
Slide 6
Slide 6 text
@jeanneboyarsky
• JUnit 5?
• JUnit 4?
• None
6
Slide 7
Slide 7 text
@jeanneboyarsky
About Me
• Java Champion
• Author
• Developer at NYC
bank for 19+
years
• FIRST Robotics
Mentor
7
Slide 8
Slide 8 text
@jeanneboyarsky
Pause for a Commercial
8
Java certs
•Java 8
•Java 11
•Java 17 next
Book giveaway at end!
Slide 9
Slide 9 text
@jeanneboyarsky
Another Commercial
9
Slide 10
Slide 10 text
@jeanneboyarsky
After this presentation
• Presentation:
http://speakerdeck.com/
boyarsky
• Samples and code we write
together:
https://github.com/boyarsky/
2021-kcdc-junit5
10
Slide 11
Slide 11 text
@jeanneboyarsky
Agenda
⭐ Intro to JUnit 5
• Common testing patterns
• Brief look back at history
(JUnit 3 and 4)
• JUnit 5 extensions
• Interactive TDD live coding
11
Slide 12
Slide 12 text
@jeanneboyarsky
• Released Sept 10,
2017
• Requires Java 8
• Today sprinkling in
Java 9+ features
12
Slide 13
Slide 13 text
@jeanneboyarsky
Basic Flow
13
class ReportTest
{
private Report report
;
@BeforeEac
h
void setUp()
{
report = new Report()
;
}
@Tes
t
void createRow()
{
}
}
No need to
make public
Runs before
each test
Can have 1+ tests
Slide 14
Slide 14 text
@jeanneboyarsky
Common Class name patterns
14
Unit Tests Integration Tests
Test*.java IT*.java
*Test.java *IT.java
*Tests.java *ITCase.java
*TestCase.java
Slide 15
Slide 15 text
@jeanneboyarsky
Common test name examples
15
Method names
testCreateRow()
createRow()
createRow_forNullParams()
shouldHandleNullParams()
Slide 16
Slide 16 text
@jeanneboyarsky
Assert Equals
16
@Tes
t
void createRow()
{
String expected =
"Jeanne Boyarsky,Intro to JUnit 5"
;
String actual = report.createRow
(
"Jeanne Boyarsky", "Intro to JUnit 5")
;
assertEquals(expected, actual, "row")
;
}
Optional message
Order matters
Slide 17
Slide 17 text
@jeanneboyarsky
Message on failing assert
17
row ==> expected:
but was:
Expected :Jeanne Boyarsk
y
Actual :Jeanne Boyarsky,Intro to JUnit 5
Remember that order matters!
Message
Slide 18
Slide 18 text
@jeanneboyarsky
BeforeAll
18
class ReportWithCacheTest
{
private static Map CACHE
;
private ReportWithCache report
;
@BeforeAl
l
static void createCache()
{
CACHE = Map.of("Intro to JUnit 5”
,
"Jeanne Boyarsky")
;
}
@BeforeEac
h
void setUp()
{
report = new ReportWithCache(CACHE)
;
}
Runs once
static
Slide 19
Slide 19 text
@jeanneboyarsky
BeforeAll
19
@Tes
t
void createRow_match()
{
String expected =
"Jeanne Boyarsky,Intro to JUnit 5"
;
String actual = report.createRow
(
"Intro to JUnit 5")
;
assertEquals(expected, actual, "row")
;
}
@Tes
t
void createRow_noMatch()
{
String expected = "Unknown,Lunch"
;
String actual = report.createRow("Lunch")
;
assertEquals(expected, actual, "default value")
;
}
@BeforeEach runs twice
@BeforeAll runs once
Slide 20
Slide 20 text
@jeanneboyarsky
Review: fill in the blanks
20
class BlanksTest {
private String z
;
@_________
_
static void a() {
// lookup next i
d
}
@_________
_
static void b() { Target.cache.clear(); }
@_________
_
void c() { z = “test data"; }
@_________
_
void d() { z = null; }
@___
_
void e() { assertNotNull(z); }
}
BeforeAll
AfterAll
BeforeEach
AfterEach
Test
Slide 21
Slide 21 text
@jeanneboyarsky
Review: what is output?
21
class FlowTest {
private static String flow = "";
@BeforeAl
l
static void before() { flow+="x"; }
@AfterAl
l
static void after() { System.out.println(flow); }
@BeforeEac
h
void setUp() { flow+="y"; }
@AfterEac
h
void tearDown() { flow+="-"; }
@Tes
t
void one() { flow+="z"; }
@Tes
t
void two() { flow+="z"; }
}
xyz-yz-
Slide 22
Slide 22 text
@jeanneboyarsky
How about now?
22
class FlowTest {
private static String flow = "";
@BeforeAl
l
static void before() { flow+="x"; }
@AfterAl
l
static void after() { System.out.println(flow); }
@BeforeEac
h
void setUp() { flow+="y"; }
@AfterEac
h
void tearDown() { flow+="-"; }
@Tes
t
void one() { flow+="1"; }
@Tes
t
void two() { flow+="2"; }
}
“deterministic but
non-obvious” order
do not rely on it
Slide 23
Slide 23 text
@jeanneboyarsky
If you really need order
23
@TestMethodOrder(MethodOrderer.OrderAnnotation.class
)
class OrderedFlowTest
{
private static String flow = ""
;
@AfterAl
l
static void after(){ System.out.println(flow); }
@Tes
t
@Order(1
)
void one() { flow+="1"; }
@Tes
t
@Order(2
)
void two() { flow+="2"; }
}
12
Are you *sure* you
need order?
@jeanneboyarsky
Assert Examples
25
assertNotNull(“")
;
assertTrue(“".isEmpty())
;
assertNotSame("", new String(“"))
;
assertLinesMatch(List.of("xyz"),
Arrays.asList("xyz"))
;
All have optional last
parameter with message
Slide 26
Slide 26 text
@jeanneboyarsky
Review: fill in the blanks
26
@Tes
t
void asserts()
{
String message = "sum"
;
int sum = Adder.sum(1, 2, 3)
;
______________(sum == 6)
;
_______________(_____, _______, __________)
;
}
assertEquals
assertTrue
6 sum message
Slide 27
Slide 27 text
@jeanneboyarsky
assertTrue vs assertEquals
27
org.opentest4j.AssertionFailedError:
expected: but was:
org.opentest4j.AssertionFailedError:
sum ==> expected: <6> but was: <7>
Use most specific
assertion you can
Slide 28
Slide 28 text
@jeanneboyarsky
Agenda
✅ Intro to JUnit 5
⭐ Common testing patterns
• Brief look back at history
(JUnit 3 and 4)
• JUnit 5 extensions
• Interactive TDD live coding
28
Slide 29
Slide 29 text
@jeanneboyarsky
assertThrows
29
@Tes
t
void throwsException()
{
assertThrows(IllegalArgumentException.class,
() -> Exceptions.validate(null))
;
}
org.opentest4j.AssertionFailedError:
Expected java.lang.IllegalArgumentException to be thrown,
but nothing was thrown.
Slide 30
Slide 30 text
@jeanneboyarsky
Which Better?
30
@Tes
t
void throwsException()
{
assertThrows(IllegalArgumentException.class,
() -> Exceptions.validate(null))
;
}
@Tes
t
void message()
{
IllegalArgumentException actual =
assertThrows(IllegalArgumentException.class,
() -> Exceptions.validate(null))
;
assertEquals("str can't be null",
actual.getMessage())
;
}
Slide 31
Slide 31 text
@jeanneboyarsky
assertTimeout
31
@Tes
t
void tooLong()
{
assertTimeout(Duration.ofSeconds(2),
this::sketchyCode)
;
}
private void sketchyCode()
{
try
{
Thread.sleep(5_000)
;
} catch (InterruptedException e)
{
// ignor
e
}
}
org.opentest4j.AssertionFailedError:
execution exceeded timeout of 2000 ms by 3005 ms
Slide 32
Slide 32 text
@jeanneboyarsky
assertAll - groups
32
@Tes
t
void bean()
{
Bean bean = new Bean("J", "B")
;
assertAll("name"
,
() -> assertEquals("Jeanne",
bean.getFirstName(), "first")
,
() -> assertEquals("Boyarsky",
bean.getLastName(), "last"))
;
}
DefaultMultiCauseException: name (2 failures)
org.opentest4j.AssertionFailedError: first ==>
expected: but was:
org.opentest4j.AssertionFailedError: last ==>
expected: but was:
Slide 33
Slide 33 text
@jeanneboyarsky
Side note - records
33
public record Record (String firstName,
String lastName) { }
@Tes
t
void record()
{
Record record = new Record("J", "B")
;
Record expected = new Record
(
“Jeanne", "Boyarsky")
;
assertEquals(expected, record, "name")
;
}
org.opentest4j.AssertionFailedError: name ==> expected:
but was:
Slide 34
Slide 34 text
@jeanneboyarsky
assertAll - dependencies
34
@Tes
t
void bean()
{
Bean bean = null
;
assertAll("found name”
,
() ->
{
assertNotNull(bean)
;
assertAll("name values"
,
() -> assertEquals("Jeanne",
bean.getFirstName(), "first")
,
() -> assertEquals("Boyarsky",
bean.getLastName(), "last"))
;
})
;
}
org.opentest4j.AssertionFailedError: expected: not
org.opentest4j.MultipleFailuresError: found name (1 failure)
org.opentest4j.AssertionFailedError: expected: not
Doesn’t run
inner assertAll
semicolons
because
lambda blocks
Slide 35
Slide 35 text
@jeanneboyarsky
Assuming
35
@Tes
t
void bean()
{
Bean bean = null
;
assumeFalse(bean == null, "no name")
;
assertEquals("Jeanne",
bean.getFirstName(), "first")
;
assertEquals("Boyarsky",
bean.getLastName(), "last")
;
}
Reports test is ignored Remember - simplest
approach is best
Slide 36
Slide 36 text
@jeanneboyarsky
Assumptions
36
Annotation Parameters
assumeTrue boolean or BooleanSupplier
optional message
assumeFalse boolean or BooleanSupplier
optional message
assumingThat boolean or BooleanSupplier
Executable
(runs the Executable only
if the assumption is true)
Slide 37
Slide 37 text
@jeanneboyarsky
Running only on Linux
37
@Tes
t
void packagedOnServer()
{
assumeTrue(System.getProperty(“os.name"
)
.toLowerCase().contains("linux"),
"skip if not linux")
;
// assertions her
e
}
@Tes
t
@EnabledOnOs(OS.LINUX
)
void linuxAnnotation()
{
// assertions her
e
}
Simplest approach is
still best
Also reports as
ignored
Slide 38
Slide 38 text
@jeanneboyarsky
OS Annotations
38
Annotations
EnabledOnOS
DisabledOnOS
@EnabledOnOs(LINUX
)
@EnabledOnOs({OS.LINUX, OS.MAC }
)
OS
WINDOWS AIX
MAC SOLARIS
LINUX OTHER
Slide 39
Slide 39 text
@jeanneboyarsky
Disabling Entirely
39
@Disable
d
@Tes
t
void uhOh()
{
// assertions her
e
}
Also reports as ignored
Slide 40
Slide 40 text
@jeanneboyarsky
Display Names
40
@DisplayName("Requirement 123"
)
class DisplayNameTest
{
@Tes
t
@DisplayName("use case 1"
)
void negativeNumber()
{
}
}
Slide 41
Slide 41 text
@jeanneboyarsky
Tags & Build Tools
41
@Tag("slow"
)
class TagTest
{
@Tes
t
@Tag("full-suite"
)
void test()
{
}
}
test
{
useJUnitPlatform {
excludeTags 'slow', 'abc
'
}
}
maven-surefire-plugi
n
slow
Slide 42
Slide 42 text
@jeanneboyarsky
Tags in the IDE
42
Slide 43
Slide 43 text
@jeanneboyarsky
Repeated Test
43
@RepeatedTest(100
)
void threadsDoNotFail()
{
}
Runs 100 times.
Fails test if any fail
Slide 44
Slide 44 text
@jeanneboyarsky
Testing System.out.println
44
class HelloWorldTest
{
private PrintStream originalSystemOut
;
private ByteArrayOutputStream mockSystemOut
;
@BeforeEac
h
void setUp()
{
mockSystemOut = new ByteArrayOutputStream()
;
System.setOut(new PrintStream(mockSystemOut))
;
}
@AfterEac
h
void restoreSystemOut()
{
System.setOut(originalSystemOut)
;
}
@Tes
t
void prints()
{
HelloWorld.main()
;
String actual = mockSystemOut.toString()
;
assertEquals("Hello World", actual.strip())
;
}
}
Warning: be
careful if running
multi-threaded
Slide 45
Slide 45 text
@jeanneboyarsky
Agenda
✅ Intro to JUnit 5
✅ Common testing patterns
⭐ Brief look back at history
(JUnit 3 and 4)
• JUnit 5 extensions
• Interactive TDD live coding
45
Slide 46
Slide 46 text
@jeanneboyarsky 46
JUnit 3.X JUnit 4.X JUnit 5.X
Released ? 2006 2017
Superclass TestCase No No
Package junit
.framework
org.junit org.junit
.jupiter….
Minimum
Java version
? 5 8
Slide 47
Slide 47 text
@jeanneboyarsky 47
JUnit 3.X JUnit 4.X JUnit 5.X
assertEquals [message,]
expected,
actual
[message,]
expected,
actual
expected,
actual
[,message]
Setup method
name
setUp() any any
Access
control
public public package
private or
higher
Slide 48
Slide 48 text
@jeanneboyarsky 48
JUnit 3.X JUnit 4.X JUnit 5.X
Test names test* Any Any
Skipping a
test
Comment
it out
@Ignore @Disabled
Slide 49
Slide 49 text
@jeanneboyarsky
Agenda
✅ Intro to JUnit 5
✅ Common testing patterns
✅ Brief look back at history
(JUnit 3 and 4)
⭐ JUnit 5 extensions
• Interactive TDD live coding
49
Slide 50
Slide 50 text
@jeanneboyarsky
Parameterized Tests
50
Dependenc
y
testImplementatio
n
'org.junit.jupiter:junit-jupiter-params:5.7.2
'
enum Env { DEV, QA, PROD}
;
@ParameterizedTes
t
@EnumSource(Env.class
)
void byEnum(Env env)
{
}
Slide 51
Slide 51 text
@jeanneboyarsky
Parameterized Tests
51
@ParameterizedTes
t
@ValueSource(strings = { "SpaceX", "Boeing"}
)
void byValue(String company)
{
}
Supports all primitives, strings, and classes
@jeanneboyarsky
Hamcrest Matchers
54
Dependenc
y
testImplementation 'org.hamcrest:hamcrest-library:2.2
'
import org.junit.jupiter.api.Test
;
import static org.hamcrest.CoreMatchers.containsString
;
import static org.hamcrest.MatcherAssert.assertThat
;
public class HamcrestTest
{
@Tes
t
void matcher()
{
assertThat("kc", containsString("kc"))
;
}
}
Slide 55
Slide 55 text
@jeanneboyarsky
Common Matchers
55
Type Examples
Combiners anyOf, allOf, both, either
Lists hasItem, is
By type any
Equality equalTo, nullValue, notNull,
containsString, startsWith,
endsWith
Slide 56
Slide 56 text
@jeanneboyarsky
Mockito
56
Dependenc
y
testImplementation 'org.mockito:mockito-junit-jupiter:3.12.1
'
testImplementation 'org.mockito:mockito-core:3.12.1
'
@ExtendWith(MockitoExtension.class
)
public class MockingTest
{
private Processor processor
;
@Moc
k
Function functionMock
;
@BeforeEac
h
void setUp()
{
processor = new Processor(functionMock)
;
}
Slide 57
Slide 57 text
@jeanneboyarsky
Mockito
57
@Tes
t
void process()
{
String text = "abc"
;
int expected = 42
;
when(functionMock.apply(text)
)
.thenReturn(expected)
;
int actual = processor.process(text)
;
assertEquals(expected, actual, "result")
;
verify(functionMock, times(1)).apply(text)
;
}
}
Slide 58
Slide 58 text
@jeanneboyarsky
Common Verifiers
58
Examples
atLeastOnce(), atLeast(int)
atMostOnce(), atMost()
times()
never()
Also useful:
@Mock(lenient=true)
Slide 59
Slide 59 text
@jeanneboyarsky
JUnit Pioneer
59
Dependenc
y
testImplementation 'org.junit-pioneer:junit-pioneer:1.4.2
'
• Retry on failure (no param
version)
• Threadsafe scheduler for
System.in/out
• And so much more
Slide 60
Slide 60 text
@jeanneboyarsky
JUnit Pioneer
60
Dependenc
y
testImplementation 'org.junit-pioneer:junit-pioneer:1.4.2
'
• Retry on failure (no param
version)
• Threadsafe scheduler for
System.in/out
• And so much more
Slide 61
Slide 61 text
@jeanneboyarsky
Converting from JUnit 4
61
• IntelliJ does this well
• Before that, I wrote a program
which was adopted:
https://github.com/junit-pioneer/
convert-junit4-to-junit5
Slide 62
Slide 62 text
@jeanneboyarsky
Agenda
✅ Intro to JUnit 5
✅ Common testing patterns
✅ Brief look back at history
(JUnit 3 and 4)
✅ JUnit 5 extensions
⭐ Interactive TDD live coding
62
Slide 63
Slide 63 text
@jeanneboyarsky
Live TDD
63
• Let’s write some code!
• Then book giveaway