How to Test an Abstract Class with JUnit?
Last Updated :
29 Oct, 2024
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="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.
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 Test a Smart Contract with Remix?
The Remix IDE is an open-source development environment for building, testing, and deploying smart contracts on the Ethereum blockchain. One of the most notable features of Remix IDE is its integration with the Solidity programming language. Solidity is a contract-oriented language that is specifica
7 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 Test PHP Code With phpUnit?
Testing PHP code is a critical aspect of software development, ensuring that applications function as intended and maintain their integrity over time. PHPUnit stands out as a premier testing framework for PHP, empowering developers to create comprehensive test suites and validate their code effectiv
4 min read
Typescript Abstract Class
TypeScript, a superset of JavaScript, introduces many features to improve code organization, readability, and maintainability. One of these features is abstract classes. This article will explain what abstract classes are, how they work, and how to use them effectively in your TypeScript projects.Th
3 min read
Abstract Method in Java with Examples
In Java, Sometimes we require just method declaration in super-classes. This can be achieved by specifying the Java abstract type modifier. Abstraction can be achieved using abstract class and abstract methods. In this article, we will learn about Java Abstract Method. Java Abstract MethodThe abstra
6 min read
Constructor in Java Abstract Class
Constructor is always called by its class name in a class itself. A constructor is used to initialize an object not to build the object. As we all know abstract classes also do have a constructor. So if we do not define any constructor inside the abstract class then JVM (Java Virtual Machine) will g
4 min read
How to Ignore a Class in TestNG?
In TestNG, ignoring a class can be done using various methods like the @Test(enabled = false) annotation, excluding it in the testng.xml file, or using groups to control test execution. These techniques give you flexibility in managing your test suite, ensuring that only relevant tests are executed.
4 min read
Abstract Classes in Python
In Python, an abstract class is a class that cannot be instantiated on its own and is designed to be a blueprint for other classes. Abstract classes allow us to define methods that must be implemented by subclasses, ensuring a consistent interface while still allowing the subclasses to provide speci
5 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