Why Test, and Your First JUnit 6 Test

First lesson of the Testing with JUnit course, free to read: about 15 minutes, 3 exercises and 3 quiz questions. This is the catalogue's base lesson; in the student space, it is rewritten for your profile.

Objectives

  • Explain what an automated unit test brings
  • Write a first JUnit 6 test structured as Arrange / Act / Assert
  • Run tests with Maven or Gradle and read the report

Unit testsJUnit 6 (Jupiter)Arrange / Act / Assert structure

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);
    }
}
  • @Test marks a test method. Since JUnit 5 (and still in JUnit 6), the class and methods can stay package-private (no public).
  • assertEquals(expected, actual): always put the expected value first, otherwise the failure message will be misleading.
  • The test name describes the scenario: method_situation_expectedResult is 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:

  1. Red: write a test that fails (the feature does not exist yet);
  2. Green: write the simplest code that makes it pass;
  3. 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 main that 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.

  1. CodeDifficulty 1Server sandbox2 hints

    My first test runner

    Reproduce the principle of assertEquals. Write a method static boolean verifierEgal(String nom, int attendu, int obtenu) that prints PASS nom or FAIL nom: attendu X, obtenu Y and returns the result.

    Each input line describes a case a b attendu. For each case, call Calculatrice.ajouter(a, b) and check the result with the name ajouter(a, b). Finish with Ré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; }
    }
  2. CodeDifficulty 2Server sandbox2 hints

    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 exactly motSimple ("kayak" → true), majuscules ("Radar" → true), phraseAvecEspaces (the sentence above → true), and nonPalindrome ("java" → false), printed as PASS nom or FAIL nom. Then, for each input line, print mot -> true or mot -> 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
        }
    }
  3. Multiple choiceDifficulty 11 hint

    Argument order

    In JUnit’s assertEquals(a, b), what should a and b represent?

    • a = actual value, b = expected value
    • a = expected value, b = actual value
    • The order does not matter

End-of-lesson quiz

  1. Which import corresponds to JUnit 6’s @Test annotation?

    • org.junit.Test
    • org.junit.jupiter.api.Test
    • java.test.Test
  2. In which step of a test do you call the method under test?

    • Arrange
    • Act
    • Assert
  3. 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

  1. JUnit Assertions and Testing Exceptions18 min
  2. Test Lifecycle and Organization16 min
  3. Parameterized Tests16 min
  4. Isolating Code: Test Doubles and Mockito20 min

Testing with JUnit · Intermediate · 2 h 25 min