How to Test an Abstract Class with JUnit?
Last Updated :
23 Jul, 2025
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 Structure
After the project creation done, set the file structure as shown in the below image:
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="https://maven.apache.org/POM/4.0.0"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://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.
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:
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
- 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.
- 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.
Similar Reads
How to Test API with REST Assured? REST Assured is a Java library that provides a domain-specific language (DSL) for writing powerful, easy-to-maintain tests for RESTful APIs. It allows you to specify the expectations for HTTP responses from a RESTful API, and it integrates seamlessly with JUnit, the most popular testing framework fo
5 min read
How to create unit tests in eclipse? Unit testing, also known as component testing, involves testing small pieces of code to ensure that their actual behavior matches the expected behavior. Eclipse, a widely used IDE for Java development, is also commonly used for testing purposes. In Java, JUnit is the preferred framework for unit tes
6 min read
How to assert that two Lists are equal with TestNG? Comparing collections, such as lists, is one of the common activities while validating test results in TestNG. In order to validate that two lists are equal, TestNG gives powerful assert methods by using an Assert class extended for working with collections. When the necessity appears to compare two
3 min read
JUnit 5 @Nested Test Classes JUnit 5 introduced several powerful features to make testing in Java more flexible and expressive. One of these features is the @Nested test classes, which allows for better organization of test cases by logically grouping them inside an outer test class. This can be particularly useful when dealing
6 min read
How to get a list of all the test methods in a TestNG class? In order to get all the test methods in a TestNG class, one can use the ITestContext or IAnnotationTransformer interfaces that are available in TestNG. The below example uses the ITestContext interface and allows access to the test methods while the test is running.Approach to Listing All Test Metho
3 min read
Nested Map Assertions in Java with JUnit In Java applications, nested data structures are commonplace, especially when dealing with configurations, JSON responses, or complex data representations. One such structure is the nested map. This article focuses on the theory behind nested maps, their structures, and the techniques used for asser
6 min read