Announcing Bito’s free open-source sponsorship program. Apply now

Get high quality AI code reviews

Unit Test Example Java: Java Explained

Table of Contents

Java unit testing is an indispensable practice for any Java developer. By testing small units of code, developers can verify that each component functions as intended before integrating them into a complex system.

In this comprehensive guide, we will explore the fundamentals of unit testing in Java. We will cover test frameworks, project setup, writing effective tests, integration, best practices, and more.Whether you are new to unit testing or looking to take your skills to the next level, this article will provide you with a strong foundation. Let’s dive in!

Unit testing involves testing individual units of code, typically classes, methods, or functions, in isolation to verify they work as expected. Some key advantages of unit testing in Java include:

  • Early bug detection – Catch issues early in the development cycle, preventing bugs from propagating downstream.
  • Improved code quality – Unit tests encourage following best practices like loose coupling and high cohesion.
  • Confidence in refactoring – Existing tests provide safety net when refactoring code.
  • Facilitates change – Simpler to modify code when you have tests to validate changes.
  • Documentation – Tests describe how code should function from a user’s perspective.

Writing good unit tests requires knowledge of some key concepts like test structure, mocking, stubbing, and more. Let’s go over some fundamentals before diving into examples.

Getting Started with Unit Testing in Java

To start unit testing Java code, you need:

  1. A unit testing framework like JUnit or TestNG
  2. Build tools like Maven or Gradle to execute tests
  3. Project structure separating test code from main code

Test Frameworks

Java has several popular open-source frameworks for writing and running unit tests:

  • JUnit – The most common Java unit testing framework. Provides annotations to identify test methods and assertions for validating results.
  • TestNG – Testing framework inspired by JUnit with added features like annotations, test groups and parallel execution.
  • Mockito – A mocking framework that lets you stub dependencies and simulate behaviors.

For this guide, we will use JUnit 5 to illustrate concepts, but the general principles apply to any framework.

Project Setup

We need a proper project structure and build configuration to compile, run and report on our tests. Here are the key steps:

  1. Use a build tool like Maven or Gradle for your project. They will manage dependencies and provide commands to execute tests.
  2. Create a src/test/java folder to store your test code separately from src/main/java production code. Mirror the package structure.
  3. Add your chosen testing libraries as dependencies in pom.xml (Maven) or build.gradle (Gradle).
  4. Configure the build tool to run tests and generate reports.

This separation and automation makes managing and running tests much easier.

Writing Effective Unit Tests

When writing good unit tests, you want to optimize for the F.I.R.S.T. principles:

  • Fast – Tests should execute quickly.
  • Independent – Tests should not depend on other tests.
  • Repeatable – Tests should work in any environment.
  • Self-Validating – Tests should have a boolean output – pass or fail.
  • Timely – Tests need to be written in a timely manner, not after all code is written.

Let’s look at some best practices guided by these principles.

Structure Using AAA Pattern

A good unit test follows the Arrange-Act-Assert (AAA) structure:

  • Arrange – Set up the test data, inputs and mocks needed to execute the test.
  • Act – Invoke the method or component under test.
  • Assert – Use assertions to verify the test result expectations.

This AAA pattern produces fast, focused and readable unit tests. For example:

@Test
void shouldCalculateDiscount() {

  // Arrange 
  DiscountCalculator calculator = new DiscountCalculator();
  Order order = new Order(100);

  // Act
  double discount = calculator.calculateDiscount(order);

  // Assert
  Assertions.assertEquals(5.0, discount);

}

Best Practices

Follow these best practices when writing unit tests:

  • Test one behavior – Each test case should verify one specific behavior or requirement. Keep tests focused.
  • Isolate code – Avoid external dependencies like databases or network calls. Use mocking/stubbing to isolate code under test.
  • Validate edge cases – Test boundary conditions and invalid inputs to improve robustness.
  • Use good assertions – Use JUnit assertions like assertEquals() and assertTrue() to test expected results.
  • Name tests clearly – Use intention-revealing names like shouldCalculateAverage() to describe behavior.
  • Reflect code changes – Update tests to reflect modifications in code to prevent regressions.
  • Test negative scenarios – Verify that code handles bad data and exceptions properly.

Applying these best practices will improve your test code quality.

Mocking and Stubbing Dependencies

When unit testing a class, you often need to mock/stub its dependencies.

For example, consider a VideoService class that depends on a NetworkService class:

public class VideoService {

  private NetworkService network;

  public VideoService(NetworkService network) {
    this.network = network;
  }

  public void streamVideo(String id) {
    // calls network service to stream video
    network.download(videoUrl); 
  }

}

We want to test VideoService in isolation without making actual network calls. To do this:

  • Use Mockito to create a mock NetworkService.
  • Stub the download() method to return a dummy response for testing purposes.

This allows us to test VideoService independently:

// Create mock 
NetworkService mockNetwork = Mockito.mock(NetworkService.class);

// Stub download method  
Mockito.when(mockNetwork.download(videoUrl))
      .thenReturn(dummyResponse); 

// Pass mock to video service      
VideoService videoService = new VideoService(mockNetwork);

// Test streamVideo method
videoService.streamVideo("abc123"); 

// Verify if download was called
Mockito.verify(mockNetwork).download(videoUrl);

Using proper mocking and stubbing results in fast, isolated and repeatable unit tests.

Integrating Unit Tests into Your Workflow

To get the most benefit from unit testing, they need to be integrated into the development workflow.

Executing Tests

There are two main ways to run tests:

IDE

  • IDEs like Eclipse and IntelliJ provide built-in support for running JUnit tests and viewing results.
  • Easily re-run tests on code changes.

Build Tools

  • Use Maven or Gradle commands to run tests and generate reports.
  • Can be automated on CI/CD pipelines.

Regardless of approach, aim to run tests frequently during development to catch issues early.

Generating Reports

Most testing frameworks provide basic pass/fail test results. To get deeper insights:

  • Use plugins like the Maven Surefire Plugin to generate reports in different formats like HTML, XML, etc.
  • Configure tools like Jenkins to publish visual test reports.
  • Track metrics like test coverage and pass percentage over time.

Comprehensive reporting provides visibility into the testing process.

Mocking Frameworks

In addition to Mockito, here are some other popular mocking frameworks for Java:

  • EasyMock – Provides extensive support for mocking classes, interfaces and methods.
  • PowerMock – Allows mocking static methods, constructors, final classes and more.
  • JMockit – Integrates mocks directly into test code for seamless usage.

Evaluating their capabilities can help choose the right one for your needs.

Unit Testing Example in Java

Let’s walk through a hands-on example to put these concepts into practice.

We will test a simple Calculator class that performs arithmetic operations:

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 IllegalArgumentException("Cannot divide by 0");
    }
    return a / b;
  }

}

To test this class:

1. Create test file

Mirror package structure and create a CalculatorTest class:

package com.example.app;

import org.junit.jupiter.api.*;

public class CalculatorTest {

  // tests will go here

}  

2. Initialize calculator

Use the @BeforeEach annotation to initialize a new calculator instance before each test:

private Calculator calculator;

@BeforeEach
void setup() {
calculator = new Calculator();
}

3. Write test methods

Write a test method to verify each method behaves correctly:

@Test
void shouldAddNumbers() {

  // Arrange
  int a = 5;
  int b = 10;
  
  // Act
  int result = calculator.add(a, b);

  // Assert 
  Assertions.assertEquals(15, result);

}

@Test 
void shouldSubtractNumbers() {

  // Arrange
  int a = 15;
  int b = 10;

  // Act
  int result = calculator.subtract(a, b);
  
  // Assert
  Assertions.assertEquals(5, result);
  
}

4. Execute tests

Run the tests using JUnit and validate they pass.

This hands-on example demonstrates how easy it is to test Java code using principles we discussed!

Mocking Example with Mockito

Let’s look at an example using Mockito for mocking:

Suppose we have an EmailService that depends on a SMTPServer interface:

public class EmailService {

  private SMTPServer smtpServer;

  public EmailService(SMTPServer smtpServer) {
    this.smtpServer = smtpServer;
  }

  public void sendEmail(String to, String body) {
    // logic to send email
    smtpServer.connect();
    // ...
  }

}

public interface SMTPServer {

  void connect();

  // other methods..
  
}

Here is how we can test EmailService by mocking SMTPServer:

@Test
void shouldSendEmail() {

  // Create mock
  SMTPServer smtpMock = Mockito.mock(SMTPServer.class);
  
  // Stub connect method
  Mockito.when(smtpMock.connect()).thenReturn(true);

  // Inject mock and test  
  EmailService emailService = new EmailService(smtpMock); 
  emailService.sendEmail("test@example.com", "Hello World!");

  // Verify connect() called
  Mockito.verify(smtpMock).connect();

}

This allows us to test EmailService in isolation without dealing with real SMTP connections.

JUnit vs TestNG

JUnit and TestNG are the two most popular testing frameworks in Java. Here is a quick comparison:

FeatureJUnitTestNG
Annotations@Test, @Before, etc.@Test, @BeforeMethod, etc.
AssertionsAssert classTestNG assertions
Parallel executionRequires pluginsBuilt-in support
GroupingCategories via @CategoryGroups using @Test(groups)
Parameterization@ParameterizedTest@DataProvider
DependsOnLimited supportSupported

Both provide a robust set of features to write tests. Evaluate them to choose the best option for your needs.

Conclusion

Unit testing is a key technique for building high-quality Java applications. Using frameworks like JUnit allows creating fast, repeatable and automated tests.By leveraging these concepts, you can start reaping the benefits of unit testing including improved code quality, faster bug detection and increased productivity.The investment put into creating a good test suite pays dividends down the road through more maintainable and robust code. Get started unit testing your Java code today!

Picture of Nisha Kumari

Nisha Kumari

Nisha Kumari, a Founding Engineer at Bito, brings a comprehensive background in software engineering, specializing in Java/J2EE, PHP, HTML, CSS, JavaScript, and web development. Her career highlights include significant roles at Accenture, where she led end-to-end project deliveries and application maintenance, and at PubMatic, where she honed her skills in online advertising and optimization. Nisha's expertise spans across SAP HANA development, project management, and technical specification, making her a versatile and skilled contributor to the tech industry.

Written by developers for developers

This article was handcrafted with by the Bito team.

Latest posts

Mastering Python’s writelines() Function for Efficient File Writing | A Comprehensive Guide

Understanding the Difference Between == and === in JavaScript – A Comprehensive Guide

Compare Two Strings in JavaScript: A Detailed Guide for Efficient String Comparison

Exploring the Distinctions: == vs equals() in Java Programming

Understanding Matplotlib Inline in Python: A Comprehensive Guide for Visualizations

Top posts

Mastering Python’s writelines() Function for Efficient File Writing | A Comprehensive Guide

Understanding the Difference Between == and === in JavaScript – A Comprehensive Guide

Compare Two Strings in JavaScript: A Detailed Guide for Efficient String Comparison

Exploring the Distinctions: == vs equals() in Java Programming

Understanding Matplotlib Inline in Python: A Comprehensive Guide for Visualizations

Get Bito for IDE of your choice