Unit Testing a Maven Project with JUnit 5 in Visual Studio Code

As a Java developer, unit testing is an essential skill for improving the quality and maintainability of your code. In this in-depth guide, we‘ll walk through setting up and writing unit tests with JUnit 5 for a Maven project in Visual Studio Code. Whether you‘re new to unit testing or looking to boost your skills, this tutorial will provide a solid foundation and best practices you can apply in your own projects.

What are Unit Tests?

Unit testing involves writing code to verify the behavior of a specific unit, such as a function or class method, in isolation from other parts of the system. The goal is to validate that each unit of the software performs as designed. This is done by providing known inputs to the unit and asserting the expected outputs occur.

Some key benefits of unit testing include:

  • Catching bugs early in the development cycle, reducing the cost to fix them
  • Ensuring your code is working as intended as you make changes
  • Providing living documentation of how the code should work
  • Encouraging you to write more modular, maintainable code
  • Improving code quality and reducing technical debt over time

Introduction to JUnit 5

JUnit is the most popular unit testing framework for Java. JUnit 5 is the latest version and a major upgrade over JUnit 4 with new features like:

  • New annotations like @BeforeEach, @AfterEach, @Disabled
  • Nested tests with @Nested to improve organization of test classes
  • Parameterized tests to test multiple scenarios with different inputs
  • Dynamic tests with @TestFactory method for cases where the tests are not known at compile time
  • Improved assertions with more descriptive error messages

To use JUnit 5 you need to declare the junit-jupiter-api and junit-jupiter-engine dependencies in your project. JUnit 5 requires Java 8 or higher.

Introduction to Maven

Apache Maven is a popular build automation and dependency management tool for Java projects. Maven uses a Project Object Model (POM) file to configure the build process, manage dependencies, and more.

Maven provides conventions and standards for structuring your project, such as:

  • Storing source code under src/main/java
  • Storing test code under src/test/java
  • Compiled code outputted to the target directory
  • Dependency jars downloaded to the target/dependency directory

This standardized structure makes it easy to navigate any Maven project. You can build, test, and package your code into a jar using Maven commands.

Setting Up a Maven Project in VS Code

First, make sure you have the Java Development Kit (JDK) and Maven installed. Then install the Java Extension Pack in VS Code to enable Java project support and helpful features.

To create a new Maven project in VS Code:

  1. Open the Command Palette (Ctrl+Shift+P) and run "Maven: Create Maven Project"
  2. Select "maven-archetype-quickstart" archetype
  3. Enter a Group Id, Artifact Id, version and package for your project
  4. Choose a location to create the project when prompted
  5. Open the project in VS Code when asked

Your new project will contain this standardized directory structure:

my-app
|-- pom.xml
|-- src
|   |-- main  
|   |   `-- java
|   |       `-- com
|   |           `-- mycompany
|   |               `-- app
|   |                   `-- App.java
|   `-- test
|       `-- java
|           `-- com
|               `-- mycompany
|                   `-- app
|                       `-- AppTest.java
`-- target

Open the pom.xml file and add these dependencies under the tag to use JUnit Jupiter, the programming model and extension model for writing JUnit 5 tests:

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.7.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.7.1</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Now in the VS Code integrated terminal run:

mvn compile

This will download the JUnit 5 dependencies and compile the project. We‘re ready to start writing some unit tests!

Writing Unit Tests with JUnit 5

Open the AppTest.java file under src/test/java. This is where we‘ll write our test cases for the App class.

First, update the imports to use the JUnit 5 versions:

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

In JUnit 5, test methods are denoted by the @Test annotation. Let‘s add a basic test:

@Test
public void shouldAnswerWithTrue() {
    assertTrue(true);
}

This test will always pass since assertTrue with an argument of true is always true. It‘s useful as a canary test to ensure your unit tests are being detected and run.

Now let‘s add some real tests. Add these methods to App.java:

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

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

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

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

These are simple methods we can easily write unit tests for. The divide method also demonstrates an important concept to test – exception handling when an invalid argument is passed.

Add the corresponding unit tests in AppTest.java:

@Test
public void add_ShouldReturnSum() {
    assertEquals(2, App.add(1, 1));
    assertEquals(0, App.add(-1, 1)); 
    assertEquals(-2, App.add(-1, -1));
}

@Test
public void subtract_ShouldReturnDifference() {
    assertEquals(0, App.subtract(1, 1));
    assertEquals(-2, App.subtract(-1, 1));
    assertEquals(0, App.subtract(-1, -1));
}

@Test 
public void multiply_ShouldReturnProduct() {
    assertEquals(1, App.multiply(1, 1));
    assertEquals(-1, App.multiply(-1, 1));
    assertEquals(1, App.multiply(-1, -1));
}

@Test
public void divide_ShouldReturnQuotient() {
    assertEquals(1, App.divide(1, 1));
    assertEquals(-1, App.divide(-1, 1));
    assertEquals(1, App.divide(-1, -1));
}

@Test
public void divide_WithZeroDivisor_ShouldThrowException() {
    assertThrows(IllegalArgumentException.class, () -> App.divide(1, 0));
}

Each test covers multiple scenarios to verify the methods return the expected output based on the arguments passed in. The last test verifies divide throws an IllegalArgumentException if a zero divisor is passed using JUnit‘s assertThrows.

Running JUnit Tests in VS Code

There are a few ways to run the unit tests:

  1. Open the Maven view in the VS Code sidebar, expand your project, and click the "play" button on test folder
  2. Right-click on AppTest.java in the file explorer and select "Run All Tests"
  3. Open AppTest.java and click the "Run Test" link above each @Test method

The Test Runner will display the results of the tests as they run. Green checks indicate passing tests while red Xs are failures. You can click on a failed test to jump to the assertion that failed.

VS Code Test Runner showing passed and failed unit tests

If you want more details about the test run, you can also open the integrated terminal and run:

mvn test

This will output the number of tests run, skipped and failed, and execution time. The build will fail if any tests fail.

Generating a Code Coverage Report

Code coverage measures how much of your code is executed by unit tests. Aim for at least 70% coverage, with higher being better.

To generate a code coverage report, add the JaCoCo Maven plugin to your pom.xml file:

<build>
    <plugins>
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>0.8.7</version>
            <executions>
                <execution>
                    <goals>
                        <goal>prepare-agent</goal>
                    </goals>
                </execution>
                <execution>
                    <id>generate-code-coverage-report</id>
                    <phase>test</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Then run:

mvn test

This will generate a code coverage report at target/site/jacoco/index.html. Open this in your browser to view an interactive report of the coverage data.

Example JaCoCo code coverage report

The report breaks down coverage by package, class and line. Aim to cover all lines and branches in your code for comprehensive unit tests.

Best Practices for Unit Testing

To get the most value out of unit testing, follow these best practices and tips:

  • Focus on testing the behavior of the code, not the implementation details
  • Strive for readable, maintainable tests. Unclear tests are almost as bad as no tests
  • Keep tests small and focused. Each test should verify one behavior
  • Tests should be isolated. Avoid dependencies between tests
  • Tests should be deterministic. Running a test with the same data should always return the same result
  • Use clear, descriptive names for test methods. Test names should describe the scenario being tested
  • Separate your tests from your production code to keep your code modular
  • Run your tests often, ideally every time the code changes

By incorporating unit testing into your development workflow, you‘ll be able to catch regressions, ensure your code works as expected, and ultimately deliver more robust software.

Conclusion

In this guide, we covered how to set up, write, run and measure unit tests with JUnit 5 and Maven in Visual Studio Code. Unit testing is a fundamental skill for any Java developer.

The source code for this example is available on GitHub at https://github.com/yourusername/junit5-maven-example

I encourage you to clone the code and experiment with writing your own unit tests. The more you practice, the more natural writing good tests will become. Happy testing!

Similar Posts