Labs ICT
โญ Pro Login

Writing Your First Test

3 min read | JUnit Testing
โญ

Want the full learning experience?

Get structured courses, certificates, projects, and instructor support with LabsICT Pro.

Explore Pro Courses

Time to write your first real test. By the end of this lesson, you will have a working test class that you can run. Let us start with a simple example and build from there.

The Class We Want to Test

Let us say we have a Calculator class:


package com.example;

public class Calculator {

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

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

    public int multiply(int a, int b) {
        return a * b;
    }

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

This is a basic calculator with four operations. We want to make sure each method works correctly. Let us write tests for them.

Writing the Test Class

Create a new test file in the test directory:


package com.example;

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

class CalculatorTest {

    @Test
    void shouldAddTwoNumbers() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3));
    }

    @Test
    void shouldSubtractTwoNumbers() {
        Calculator calc = new Calculator();
        assertEquals(1, calc.subtract(3, 2));
    }

    @Test
    void shouldMultiplyTwoNumbers() {
        Calculator calc = new Calculator();
        assertEquals(6, calc.multiply(2, 3));
    }

    @Test
    void shouldDivideTwoNumbers() {
        Calculator calc = new Calculator();
        assertEquals(2, calc.divide(6, 3));
    }

    @Test
    void shouldThrowExceptionWhenDividingByZero() {
        Calculator calc = new Calculator();
        assertThrows(ArithmeticException.class, () -> calc.divide(10, 0));
    }
}
    

Anatomy of a Test Method

Let us break down what is happening in each test method:


@Test                            <-- Annotation that marks this as a test
void shouldAddTwoNumbers() {     <-- Method name describes the expected behavior
    Calculator calc = new Calculator();  <-- Arrange: set up the test
    assertEquals(5, calc.add(2, 3));     <-- Act & Assert: run and verify
}
    

Every test method follows the AAA pattern:

  • Arrange - Set up the test data and objects
  • Act - Execute the method you want to test
  • Assert - Verify the result is what you expected

Running Your Tests

Run the tests from the command line:


# Maven
mvn test

# Gradle
gradle test
    

Or from your IDE by right-clicking the test class and selecting "Run."

If all tests pass, you will see something like:


Tests run: 5, Failures: 0, Errors: 0, Skipped: 0

BUILD SUCCESS
    

If a test fails, JUnit tells you exactly which test failed and why. For example:


org.opentest4j.AssertionFailedError:
    expected: <5> but was: <6>
    at CalculatorTest.shouldAddTwoNumbers(CalculatorTest.java:10)
    

Test Method Naming

Notice how the test methods are named. Good test names describe the scenario and the expected behavior. Here are some naming conventions people use:


// Style 1: shouldXxx
void shouldReturnSumWhenGivenTwoNumbers() { }

// Style 2: descriptive
void add_withPositiveNumbers_returnsSum() { }

// Style 3: testXxx
void testAddition() { }

// Style 4: givenXxxWhenYyyThenZzz
void givenTwoNumbers_whenAdded_thenReturnsSum() { }
    

There is no single "right" way. Pick a style and be consistent. The key is that when a test fails, the name should tell you what went wrong.

Your Turn

Try this exercise. Create a StringHelper class with these methods:


public class StringHelper {

    public boolean isPalindrome(String str) {
        String reversed = new StringBuilder(str).reverse().toString();
        return str.equalsIgnoreCase(reversed);
    }

    public int countVowels(String str) {
        int count = 0;
        for (char c : str.toLowerCase().toCharArray()) {
            if ("aeiou".indexOf(c) != -1) {
                count++;
            }
        }
        return count;
    }

    public String reverse(String str) {
        return new StringBuilder(str).reverse().toString();
    }
}
    

Write tests for each method. Think about edge cases: what happens with empty strings? Null values? Single characters? This is how you build a solid test suite.

๐Ÿงช Quick Quiz

Which of these is the correct annotation to mark a method as a test in JUnit 5?