Week 3 - Data Structures & Testing 

Collections

Generics

Enums

Stream APIs

Unit Testing with JUnit

Debugging in Java

Practice

Assignment

Back end Track

Introduction

In Week 5 - Problem solving methods, you learned why automated testing matters and wrote your first unit tests in JavaScript. The ideas transfer directly to Java: choosing useful test cases, using the Arrange-Act-Assert pattern, reading failure output, and checking edge cases.

The difference is the tooling. In the Java ecosystem, the standard testing framework is JUnit 5.

Professional Java backends usually run many automated tests before code reaches production. Tests catch regressions early, document expected behavior, and give you confidence when you refactor. In this chapter, you’ll set up JUnit 5 with Maven, learn its core annotations and assertions, and write tests for classes you already understand.

JUnit 5 Setup

JUnit 5 is not part of the Java standard library, so you add it as a dependency through a build tool. From now on, we will use Maven for Java projects.

In IntelliJ IDEA, create a new Maven project by following these steps:

  1. Navigate to the New Project window.

    image.png

  2. In the New Project window, choose a project name and select the options highlighted below.

    Maven is selected as the build system. Java 25 is preferred. "Add sample code" is checked for convenience. The  does not have to be exactly the same, but using the same value makes the examples easier to copy and experiment with.

    Maven is selected as the build system. Java 25 is preferred. "Add sample code" is checked for convenience. The GroupId does not have to be exactly the same, but using the same value makes the examples easier to copy and experiment with.

  3. Verify the folder structure of your newly created project.

    HYFBackendTrack/
    ├── 📁 src/
    │   ├── 📁 main/
    │   │   ├── 📁 java/
    │   │   │   └── 📁 net/  # IntelliJ shortens package to "net.hackyourfuture" if "net" is empty.
    │   │   │       └── 📁 hackyourfuture/
    │   │   │           └── 📄 Main.java
    │   └── 📁 test/
    │       └── 📁 java/
    ├── 📄 README.md
    ├── 📄 .gitignore
    └── 📄 pom.xml
    
  4. Add the following dependency to the <dependencies> section of your pom.xml:

    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.14.3</version>
        <scope>test</scope>
    </dependency>
    

    <aside> 💡

    If you can’t find <dependencies> element in your pom.xml, you can add it before </project>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.14.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    

    </aside>

  5. After adding the dependency, click the Maven reload button in IntelliJ (the circular arrows icon in the Maven tool window) to download the library.

    image.png

    <aside> 💡

    If the popup is not there, you can also directly sync maven project on your maven tab

    image.png

    </aside>

Folder structure and naming conventions

Java projects follow a standard directory layout that keeps production code separate from test code:

HYFBackendTrack/
└── 📂 src/
    ├── 📂 main/
    │   └── 📂 java/
    │       └── # Your production code
    └── 📂 test/
        └── 📂 java/
            └── # Your test code

Key Conventions

💬 Why do we keep tests in a separate source root (src/test/java) instead of next to the production code?

<aside> 💡

If you don’t want to always worry about test class packages mirroring the package structure of src/main/java, you can generate the test class with IntelliJ’s “Navigate to Test” shortcut. Covered in the section below.

</aside>

Set-up a class to test

Add the below production class, Calculator.java to your project in src/main/java/net/hackyourfuture directory/package:

package net.hackyourfuture;

// src/main/java/net/hackyourfuture/Calculator.java
class Calculator {

  int add(int a, int b) {
    return a + b;
  }

  int subtract(int a, int b) {
    return a - b;
  }

  int divide(int a, int b) {
    if (b == 0) {
      throw new ArithmeticException("Cannot divide by zero");
    }
    return a / b;
  }
}

Set-up initial unit-test class

Now let’s create the initial unit-test class for Calculator .

“Generate Tests…”, “Navigate to Test” shortcut:

  1. In Calculator.java , with Calculator class symbol selected, attempt navigating to test:

    1. Ctrl + Shift + T (Windows)

    2. ⌘ + ⇧ + T (MacOS)

      The shortcut will open a new popup “Choose Test for Calculator”

      The shortcut will open a new popup “Choose Test for Calculator

    3. The opened “Choose Test for Calculator” popup finds no tests (0 found) since we haven’t written any yet.

  2. Click the “Create New Test…” option:

    image.png

  3. In the opened “Create Test” dialog, check all class methods then click “OK”:

    image.png

  4. IntelliJ then will generate the below test class for you in the right directory/package.

    package net.hackyourfuture;
    
    import org.junit.jupiter.api.Test;
    
    import static org.junit.jupiter.api.Assertions.*;
    
    class CalculatorTest {
    
    	@Test
    	void add() {
    	}
    
    	@Test
    	void subtract() {
    	}
    
    	@Test
    	void divide() {
    	}
    }
    

Test class structure

The @Test annotation

The @Test annotation marks a method as a test case. When JUnit runs, it discovers methods annotated with @Test and executes them.

Test Method Signature

Test methods need to follow these requirements from JUnit:

As you can see the test class generated by IntelliJ follows all of the above.

The JUnit requirements are for actually running your test methods. However, it is also important for your test method names to be following the naming conventions. IntelliJ could not do that for you since it doesn’t know what your test method will be testing.

Test Method Naming

Test method names should describe the expected behavior. Example: shouldAddTwoNumbers instead of add .

Clear naming makes all the difference when you’re runing 100’s, if not 1000’s, of unit-tests and going over the failed one

<aside> 💡

Naming test cases is harder than most people expect. One useful pattern is to write the method name like a short sentence:

given{inputOrSituation}_returns{expectedResult}

For the method above, you could use:

@Test
void givenTwoNumbers_returnsTheirSum() {}

</aside>

<aside> ⚠️

Make sure you import @Test from org.junit.jupiter.api.Test (JUnit 5). The older import org.junit.Test belongs to JUnit 4 and is not compatible with these examples. If your test will not run, check the import first.

</aside>

Running tests in IntelliJ

IntelliJ IDEA offers several ways to run tests:

Reading test output

After running tests, the Run panel at the bottom of IntelliJ shows:

For a failing assertEquals, the output looks something like this:

Expected :5
Actual   :4

This tells you the test expected 5, but the code returned 4. Click the failing test name to jump directly to the failing assertion.

💬 Try running an empty test method. Why do you think the test succeeds?

Writing your first test

Rename the add method in CalculatorTest to givenTwoNumbers_returnsTheirSum and let’s test that.

package net.hackyourfuture;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

// src/test/java/net/hackyourfuture/CalculatorTest.java
class CalculatorTest {

  @Test
  void givenTwoNumbers_returnsTheirSum() {
    // Arrange
    Calculator calculator = new Calculator();

    // Act
    int result = calculator.add(2, 3);

    // Assert
    Assertions.assertEquals(5, result);
  }

  @Test
	void subtract() {}

	@Test
	void divide() {}
}

Importing the whole Assertions class is useful while learning because you can type Assertions. and explore available methods with autocomplete. In everyday test code, Java developers often use static imports instead:

import static org.junit.jupiter.api.Assertions.assertEquals;

// Then in your test method you can directly use the imported method:
assertEquals(5, result);

💬 What are the advantages of using static method imports instead of importing the Assertions class?

<aside> ⌨️

</aside>

Assertions

Assertions are the core of every test. They verify that your code behaves as expected. JUnit 5 provides assertion methods through the org.junit.jupiter.api.Assertions class.

Assertion methods are straightforward and read like English: