Open In App

How to Test an Abstract Class with JUnit?

Last Updated : 29 Oct, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

Testing abstract classes in Java can be challenging due to their incomplete nature. They cannot be instantiated directly. Abstract classes are often used as the blueprint for the other classes. They can include abstract methods, which lack implementations, as well as concrete methods that are fully implemented. Testing their behavior ensures that subclasses align with the specified contract.

In this article, we will learn different ways to test the abstract class using JUnit, with suitable examples, code snippets, and outputs of the Maven project.

Prerequisites:

  • Basic understanding of Java and JUnit.
  • Unit 5 for writing and executing tests.
  • Maven for building dependency management.
  • JDK and IntelliJ IDEA installed in your system.

Abstract Class

In Java, an abstract class is a type of class that cannot be directly instantiated and is designed to be inherited by other classes. It can contain both:

  • Abstract Methods: Methods declared without implementation, requiring subclasses to provide their concrete implementations.
  • Concrete Methods: Methods with defined implementations that can be directly used by subclasses.

Abstract classes are ideal for situations where you want to provide a common base with default behavior for multiple related classes while ensuring specific methods are implemented in subclasses.

Why Can’t We Instantiate Abstract Classes Directly?

Since abstract classes can have methods without implementation, creating an instance directly would lead to unimplemented behavior, which could result in runtime errors. Java prevents this by making abstract classes non-instantiable.

Purpose of Testing Abstract Classes

When working with the abstract class, we typically want to test:

  • Concrete Methods: Even though the class itself cannot be instantiated, it may contain the methods with logic that should be verified for correctness.
  • Contracts Defined by Abstract Methods: It ensures that when subclasses implement abstract methods.

For example, if the abstract class defines the template method pattern, it may provide the sequence of method calls with some methods abstract. Testing ensures that the intended sequence is respected when a subclass is created.

Project Implementation to Test an Abstract Class With JUnit

In this example, we will set up a project to test an abstract class Vehicle using JUnit. This will include file structure, code implementations, and explanations.

Step 1: Create a New Maven Project

In IntelliJ IDEA, create a new Maven project with the following settings:

  • Name: abstract-class-testing
  • Build System: Maven

Click on the Create button.

Project Metadata

Project Structure

After the project creation done, set the file structure as shown in the below image:

Project Folder Structure

Step 2: Add the JUnit dependencies to pom.xml

Open the pom.xml file and add the below JUnit dependencies.

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gfg</groupId>
    <artifactId>abstract-class-testing</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!-- JUnit 5 dependency for testing -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.9.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>
</project>

Step 3: Define Vehicle.java (Abstract Class)

Create an abstract class Vehicle, which contains both an abstract and a concrete method.

Java
package com.example;

public abstract class Vehicle {
    private String name;

    public Vehicle(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public abstract int getNumberOfWheels();

    // Concrete method that provides common behavior
    public String start() {
        return "Vehicle " + name + " started";
    }
}

Explantation:

  • Vehicle contains an abstract method getNumberOfWheels() and a concrete method start().
  • It has a name attribute and a constructor that initializes it.
  • getNumberOfWheels() must be implemented by any subclass.
  • start() is a concrete method that returns a message indicating that the vehicle has started.

Step 4: Define Car.java (Concrete Subclass)

Create a Car class that extends Vehicle and provides the implementation for getNumberOfWheels().

Java
package com.example;

public class Car extends Vehicle {

    public Car(String name) {
        super(name);
    }

    @Override
    public int getNumberOfWheels() {
        return 4;
    }
}

Explanation:

  • Car extends Vehicle and provides the implementation for getNumberOfWheels() that returns 4.
  • It inherits the start() method from the Vehicle class.
  • Car represents the vehicle with 4 wheels.

Step 5: Define Bike.java (Concrete Subclass)

Similarly, create a Bike class that extends Vehicle and implements getNumberOfWheels().

Java
package com.example;

public class Bike extends Vehicle {

    public Bike(String name) {
        super(name);
    }

    @Override
    public int getNumberOfWheels() {
        return 2;
    }
}

Explanation:

  • Bike extends Vehicle and provides the implementation for getNumberOfWheels() that returns 2.
  • It inherits the start() method from the Vehicle class.
  • Bike represents the vehicle with 2 wheels.

Step 6: Main Class (For Direct Testing)

The main class demonstrates the usage of Vehicle, Car, and Bike.

Java
package com.example;

public class Main {
    public static void main(String[] args) {
        Vehicle car = new Car("Honda");
        Vehicle bike = new Bike("Yamaha");

        System.out.println(car.start());
        System.out.println("Car has " + car.getNumberOfWheels() + " wheels.");

        System.out.println(bike.start());
        System.out.println("Bike has " + bike.getNumberOfWheels() + " wheels.");
    }
}

Step 7: VehicleTest.java (JUnit Test Class)

Create a JUnit test class to verify the behavior of the Vehicle abstract class and its subclasses.

Java
package com.example;

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

public class VehicleTest {

    @Test
    public void testCarStartMethod() {
        Vehicle car = new Car("Honda");
        assertEquals("Vehicle Honda started", car.start());
    }

    @Test
    public void testBikeStartMethod() {
        Vehicle bike = new Bike("Yamaha");
        assertEquals("Vehicle Yamaha started", bike.start());
    }

    @Test
    public void testCarNumberOfWheels() {
        Vehicle car = new Car("Honda");
        assertEquals(4, car.getNumberOfWheels());
    }

    @Test
    public void testBikeNumberOfWheels() {
        Vehicle bike = new Bike("Yamaha");
        assertEquals(2, bike.getNumberOfWheels());
    }
}

Explanation:

testCarStartMethod()

  • Purpose: This test verifies that the start() method inherited by the Car class works as the expected. It checks whether the start() method returns the correct message for the vehicle name "Honda".
  • Expected Behavior: The method should be return the "Vehicle Honda started" when called on the Car instance.

testBikeStartMethod()

  • Purpose: This test checks the behavior of the start() method when called on the Bike instance. It ensures that start() method works consistently across the different subclasses.
  • Expected Behavior: The method should be return "Vehicle Yamaha started" when called on the Bike instance.

testCarNumberOfWheels()

  • Purpose: This test ensures that the Car class correctly implements the getNumberOfWheels() method and returning the expected value of 4.
  • Expected Behavior: The method should return 4, as the cars typically have four wheels.

testBikeNumberOfWheels()

  • Purpose: This test verifies that the Bike class implements the getNumberOfWheels() method correctly and returns the value 2.
  • Expected Behavior: The method should return 2, as the bikes generally have two wheels.

testAnonymousClassImplementation()

  • Purpose: This test uses the anonymous class to directly implement the abstract Vehicle class without creating the formal subclass. It tests both the start() and getNumberOfWheels() methods.
  • Expected Behavior:
    • The start() method should return "Vehicle Anonymous started", demonstrating that the anonymous class inherits and uses the abstract class's concrete method.
    • The getNumberOfWheels() method should return 3 and simulating the tricycle with three wheels.

Step 8: Run the Application

After the project completed, run the project, and it will display the below output in console.

Output


Step 9: Testing the Application

Now, we will run the test suites using the below maven command:

mvn test

We should see the following test results:

Test Results

This output confirms that all the tests passed successfully, verifying that both the abstract class Vehicle and its subclasses are functioning as expected.

Explanation of Test Output

  • Tests Run: 5 tests were executed, one for each of the test methods defined in the VehicleTest.
  • Failures and Errors: All tests are passed successfully (Failures: 0, Errors: 0), meaning that all the expected behavior was correctly implemented and verified.
  • Time Elapsed: The total time taken to run all the tests is minimal, it is indicating that the test suite runs efficiently.

Significance of the Output

  1. No Failures: The output confirms that the vehicle abstract class and its subclasses (Car and Bike) behaves as expected. The start() method works correctly for all the classes, and the getNumberOfWheels() method returns the right values for the each subclass.
  2. Anonymous Class Testing: The successful execution of the anonymous class test shows that we can directly the test abstract methods by creating the temporary implementations, providing flexibility in the testing.

Next Article
Article Tags :

Similar Reads