Checking your code “by hand” with System.out.println works once, but not after every change. An automated unit test is a small program that calls your code and checks the result. You can run hundreds of them in a few seconds, on every change: it is a safety net that lets you modify code without fearing you will break everything.
JUnit 6 in a Maven project
JUnit is the standard testing framework in Java. The current version, JUnit 6 (released in September 2025), requires Java 17 or later and keeps the Jupiter API (org.junit.jupiter) introduced with JUnit 5: test code is written the same way, and this course also applies to a project that is still on JUnit 5. A practical novelty: all JUnit modules (platform, Jupiter) now share the same version number.
Create the skeleton with the Maven archetype, then the Maven Wrapper (mvnw), which pins the project’s Maven version for the whole team:
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5 \
-DgroupId=fr.boutique -DartifactId=boutique -DinteractiveMode=false
cd boutique
mvn wrapper:wrapper # creates mvnw, mvnw.cmd and .mvn/; from then on we use ./mvnw
The archetype generates a pom.xml targeting Java 17 and JUnit 5.11: switch it to Java 25 and JUnit 6. The junit-bom BOM pins the version of all JUnit modules in a single place:
<properties>
<maven.compiler.release>25</maven.compiler.release>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.1.3</version> <!-- latest version on Maven Central at the time of writing -->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId> <!-- API, engine, and parameterized tests -->
<scope>test</scope>
</dependency>
</dependencies>
In a Spring Boot project, do not declare a version: Spring Boot 4’s test starters bring JUnit 6, Mockito, and AssertJ.
Production code goes in src/main/java, tests in src/test/java, with the same package.
A first test
// src/main/java/fr/boutique/Calculatrice.java
public class Calculatrice {
public int ajouter(int a, int b) { return a + b; }
}
// src/test/java/fr/boutique/CalculatriceTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatriceTest {
@Test
void ajouter_deuxPositifs_renvoieLaSomme() {
// Arrange: set up the data and the object under test
Calculatrice calc = new Calculatrice();
// Act: execute the behavior under test
int resultat = calc.ajouter(2, 3);
// Assert: verify the result
assertEquals(5, resultat);
}
}
@Testmarks a test method. Since JUnit 5 (and still in JUnit 6), the class and methods can stay package-private (nopublic).assertEquals(expected, actual): always put the expected value first, otherwise the failure message will be misleading.- The test name describes the scenario:
method_situation_expectedResultis a common convention.
Running the tests
./mvnw test # Maven via the wrapper (Surefire plugin); mvnw.cmd test on Windows
./gradlew test # Gradle (with useJUnitPlatform())
IDEs (IntelliJ IDEA, VS Code, Eclipse) show a run button next to each test. On failure, JUnit prints a message like expected: <5> but was: <6> with the faulty line.
The red / green / refactor cycle
TDD (Test-Driven Development) means writing the test before the code:
- Red: write a test that fails (the feature does not exist yet);
- Green: write the simplest code that makes it pass;
- Refactor: improve the code, with the tests guaranteeing that nothing breaks.
Even if you do not practice strict TDD, remember that a good test is fast, isolated, repeatable and checks a single behavior.
JUnit is not installed in the exercise editor: you will write small “home-made” tests in
mainthat reproduce JUnit’s logic exactly (compare expected and actual, print PASS or FAIL). The JUnit examples in the lesson are meant to be run in your Maven project.
Exercises
Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.
My first test runner
Reproduce the principle of
assertEquals. Write a methodstatic boolean verifierEgal(String nom, int attendu, int obtenu)that printsPASS nomorFAIL nom: attendu X, obtenu Yand returns the result.Each input line describes a case
a b attendu. For each case, callCalculatrice.ajouter(a, b)and check the result with the nameajouter(a, b). Finish withRésultat : R/T tests réussis.import java.util.*; public class Main { static boolean verifierEgal(String nom, int attendu, int obtenu) { // TODO return false; } public static void main(String[] args) { Scanner sc = new Scanner(System.in); Calculatrice calc = new Calculatrice(); int reussis = 0, total = 0; // TODO : lire les cas et les vérifier System.out.println("Résultat : " + reussis + "/" + total + " tests réussis"); } } class Calculatrice { int ajouter(int a, int b) { return a + b; } }Testing a palindrome function
Implement
static boolean estPalindrome(String s)that ignores case and spaces ("Esope reste ici et se repose"is a palindrome; so is the empty string).In
main, write four home-made tests named exactlymotSimple("kayak"→ true),majuscules("Radar"→ true),phraseAvecEspaces(the sentence above → true), andnonPalindrome("java"→ false), printed asPASS nomorFAIL nom. Then, for each input line, printmot -> trueormot -> false.import java.util.*; public class Main { static boolean estPalindrome(String s) { // TODO return false; } static void test(String nom, boolean attendu, boolean obtenu) { System.out.println((attendu == obtenu ? "PASS " : "FAIL ") + nom); } public static void main(String[] args) { // TODO : les quatre tests, puis l'exploration des lignes d'entrée } }Argument order
In JUnit’s
assertEquals(a, b), what shouldaandbrepresent?- a = actual value, b = expected value
- a = expected value, b = actual value
- The order does not matter
End-of-lesson quiz
Which import corresponds to JUnit 6’s @Test annotation?
- org.junit.Test
- org.junit.jupiter.api.Test
- java.test.Test
In which step of a test do you call the method under test?
- Arrange
- Act
- Assert
What does the “red” phase of TDD mean?
- Writing a failing test before writing the code
- Deleting the failing tests
- Fixing a bug in production
The rest of the course
- JUnit Assertions and Testing Exceptions18 min
- Test Lifecycle and Organization16 min
- Parameterized Tests16 min
- Isolating Code: Test Doubles and Mockito20 min