Notes on Junit 5 Basics
Junit 5 Architecture
- Junit Engine/Junit Jupiter Engine (core engine of Junit)
- Junit Jupiter API
- Junit Vintage (For legacy versions)
- Junit Extentions
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
Import dependency to pom.xml
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
General
-
@Test
annotation is to mark a method as test -
assertEquals(expected, actual)
methods to test equality@Test private void testAdd() { int actual = mathUtils.add(0, 1); assertEquals(1, actual, "Should add two numbers"); }
-
public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable)
method to check for exception thrown by a method- Here
Executable
is a@FunctionalInterface
with one methodvoid execute() throws Throwable;
@Test private void testDivide() { Executable executable = () -> mathUtils.divide(10, 0); assertThrows(ArithmeticException.class, executable, "Divide by 0 should return excpetion"); }
- Here
-
fail()
method will mark a test as fail. -
New instance created per test
- By default Junit creates new instance of the call for every test, before running every method that marked as
@Test
of that particular class - But this can be changed using
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
which is not suggested ❌@TestInstance(TestInstance.Lifecycle.PER_CLASS)
doesn’t guarantee order though ‼️- we don’t need to use static for
@BeforeAll
when usingPER_CLASS
strategy 🤦🏻♂️
- By default Junit creates new instance of the call for every test, before running every method that marked as
Life Cycle Hooks ♻️
- Junit provides 4 lifecycle hooks
@BeforeAll
- Runs before the instance is created to must be astatic
method@BeforeEach
- Runs before every@Test
method, helpful for initializing method variables@AfterAll
- Tear down method, runs after every@Test
method@AfterEach
- It is used to signal that the annotated method should be executed after each@Test
method in the current class.
Useful Annotations (Good to know)
-
@DisplayName
- Change the name of the test instead showing method name -
@Dsiabled
- Skip test method@Test @Disabled @DisplayName("Woring on TDD, Shoud skipped for now") private void incompleMethod() { double actual = mathUtils.calculateCircleArea(10); double expected = Math.PI * 100; assertEquals(expected, actual); }
Conditional Execution
-
@EnableOnOs(OS.LINUX)
@Test @EnabledOnOs(OS.LINUX) @DisplayName("Should Skip as I'm not using Linux") public void enableONLinux() { fail("On Linux this test should fail"); }
-
@EnableOrJre(JRE.JAVA_11)
-
@EnableIf
-
@EanbleIfSystemProperty
-
@EnableIfEnvironmentVariable
Handling external factors
-
@assumeTrue
- Run when an assumption is true otherwise skip this method@Test public void runIfServerIsUp() { assumeTrue(serverIsUp); // write your test here; }
Using @Nested
🎁
Group multiple tests for a method with @Nested
@Nested
@DisplayName("add method")
class AddTest {
@Test
@DisplayName("when adding two positive numbers")
public void testAddPositive() {
int actual = mathUtils.add(0, 1);
assertEquals(1, actual, "should return right sum");
}
@Test
@DisplayName("when adding two negative numbers")
public void testAddNegative() {
int actual = mathUtils.add(-10, -10);
assertEquals(-20, actual, "should return right sum");
}
}
Using assertAll
Multiple assertions inside a method. Can group multiple test without create separate methods.
@Test
@DisplayName("Test Multiply method using assertAll")
public void testMultiply() {
assertAll(
() -> assertEquals(4, mathUtils.multiply(2, 2)),
() -> assertEquals(0, mathUtils.multiply(2, 0)),
() -> assertEquals(-9, mathUtils.multiply(-3, 3))
);
}
Lazy loading message using supplier
a supplier can be passed to assert methods, it will run only when the test fails. Can save time for complex message construction.
@Test
@DisplayName("Calculate Circle Area will fail")
public void circleAreaWithWrongValueOfPI() {
double actual = mathUtils.calculateCircleArea(10);
double rightExpected = 3.1417 * 100;
double wrongExpected = Math.PI * 100;
assertEquals(rightExpected, actual, () -> "Supplier will construct this message lazily if test fails");
assertEquals(wrongExpected, actual, () -> "Supplier will construct this message lazily, fails at " + Instant.now().toString());
}
Repeat test with @RepetedTest
🔄
@ReptedTest()
can run a single test multiple times, the test will successful if all the repeated test is successful.
Junit will inject a RepetitionInfo
object to the method marked as @RepetedTest()
where information of repetition will reside.
@RepeatedTest(3)
@DisplayName("Test Multiply method should be tested 3 times")
public void testMultiplyMultipleTimes(RepetitionInfo repetitionInfo) {
assertEquals(4, mathUtils.multiply(2, 2));
}
Use @Tag
for tagging test methods
When there is lots of test then @Tag
can be helpful. Tagging different test and run them based on tag.
Then Change run configuration to run test for certain tag.
Also maven-surefire-plugin
can be configured to group and run test by tags.
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M1</version>
<configuration>
<groups>math</groups>
</configuration>
</plugin>
TestInfo and TestReporter
Junit can inject two handy class call TestInfo
and TestReporter
that can be used to print details about the test.
private TestInfo testInfo;
private TestReporter testReporter;
@BeforeEach
public void init(TestInfo testInfo, TestReporter testReporter) {
System.out.println("Before Each Method");
this.testInfo = testInfo;
this.testReporter = testReporter;
mathUtils = new MathUtils();
serverIsUp = false;
}
@Test
@DisplayName("Test Multiply method using assertAll")
@Tag("math")
public void testMultiply() {
testReporter.publishEntry("Running " + testInfo.getDisplayName() + " with tag " + testInfo.getTags());
assertAll(
() -> assertEquals(4, mathUtils.multiply(2, 2)),
() -> assertEquals(0, mathUtils.multiply(2, 0)),
() -> assertEquals(-9, mathUtils.multiply(-3, 3))
);
}
Avoid ☠️
- Do not order your test methods, It is not logics to order test methods. Every test should be independent.
- Dont modify member variable inside a test, it will not work as JUnit by default create instance of test class before running every test.