Unit tests in Java #
Several frameworks are available in Java for unit tests. Popular frameworks include:
In this section, we focus on Junit 5.
Install Junit 5 with Maven #
Junit 5 can be used in a Maven project by declaring the following dependency and plugin
<dependencies>
...
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
...
</dependencies>
<build>
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
...
</plugins>
</build>
For the latest versions, search Maven Central.
Directory layout #
Maven #
Recall that in a Maven project, by default:
- source code for unit tests is located in the directory
src/test/java
, - resources for unit tests (e.g. data) are located in the directory
src/test/resources
.
Note. You can create sub-directories (in these directories) to organize your tests and resources.
Gradle #
Junit 5 can be used with Gradle in a similar way, but the directories for unit tests and related resources and (usually) specified manually.
Hint. If you chose the board game as your project, then you can write JUnit 5 tests under
core/src/test/java
. You will also find (dummy) unit tests in the directorycore/src/test/java/dummy
.
Writing unit tests #
With Maven, any (public or protected) Java class in src/test/java
(or a subdirectory) can contain JUnit tests.
A Junit 5 test is a (public or protected) instance method :
- with
void
return type,- annotated with
org.junit.jupiter.api.Test
.
In addition, the method usually contains one or several calls to static methods of JUnit’s Assertions class, like assertEquals
or assertThrows
.
The test succeeds iff all these calls return true
.
For instance, the following class contains a single (successful) test:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MyTests {
@Test
void myTest(){
String myString = "foo";
assertEquals(true, myString.contains("oo"));
}
}
Warning. The method
assertEquals
takes the expected value as first argument, and the actual one as second argument. This has an incidence on logs in case of test failure (“expected” : XXX, “actual: “YYY”);
Hint. A string can be passed to
assertEquals
as third argument, in order to display an additional message in case of failure.
Hint. Most methods of the class Assertions are syntactic sugar (thus not strictly necessary). For instance
assertTrue(<myExpression>)
is a shortcut for
assertEquals(true, <myExpression>)
Warning. By default, the execution of a test method is interrupted as soon as an assertion fails. So subsequent instructions (for instance other assertions) will not be executed.
Checking exceptions #
The assertion assertThrows
allows checking whether the execution of a piece of code method throws an exception of a given type.
For instance the following unit test succeeds:
@Test
void testException(){
assertThrows(
ArithmeticException.class,
() -> {
int x = 2 / 0;
}
);
}
Prior and/or subsequent code #
The annotation @BeforeAll
(resp. @AfterAll
) can be used to indicate that a (static) method must be executed (only once) before (resp. after) all unit tests in the class, regardless of test successes or failures.
This can be used for costly operations, or for opening (resp. closing) a resource, like an connection or an input (resp. output) stream.
For instance
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
public class SQLTests {
Connection myConnection;
@BeforeAll
static void openConnection(){
...
}
@Test
void test1(){
...
}
@Test
void test2(){
...
}
@AfterAll
static void closeConnection(){
...
}
}
Similarly the annotation @BeforeEach
(resp. @AfterEach
) can be used to indicate that a method must be executed before (resp. after) each unit test in the class.
This can be convenient to avoid copy-pasting code.
For instance
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BoardgameTests {
Snapshot testSnapshot;
@BeforeEach
void createTestSnapshot(){
testSnapshot = new DummySnapshot();
}
@Test
void test1(){
...
}
@Test
void test2(){
...
}
}
Disabling tests #
A unit test can be disabled with the annotation @Disabled
.
As a result, it will be ignored by Maven, Gradle, and (to some extent) your IDE.
For instance:
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
public class MyTests {
@Test
@Disabled
void test1(){
...
}
@Test
void test2(){
...
}
}
Running unit tests #
from the command line #
For a Maven project, the command
mvn test
executes all phases that precede the test
phase (e.g. compile
and test-compile
) in Maven’s default lifecycle, and then executes all unit tests under src/test/java
.
Note. The source code of unit tests (and related resources) is by default excluded form the output of the
package
phase (e.g. from the jar produced by Maven).
Note. A unit test failure will prevent execution of Maven phases that are posterior to
test
. For instancemvn package
does not produce the expected jar if a test fails.
However, if needed, it is possible to execute a posterior Maven phase while skipping the unit tests, with the option
-DskipTests
. For instance:mvn package -DskipTests
within an IDE #
IDEs provide several ways to run unit tests, either:
- in isolation, or
- all tests within a class, or
- all tests within a package (including subpackages).
For instance, with IntelliJ:
- click on the green arrow to run a single unit test,
- click on the double green arrow to run all unit tests defined in the current class,
- right click on a folder and select “Run Tests” to run all tests in this package.
Note. Within an IDE, a unit test can (and most often should) be run in debug mode. For instance, with IntelliJ, right-click on the green arrow and select ‘Debug’.