JUnit is a popular testing framework in the Java ecosystem that simplifies writing and running tests. The @ExtendWith annotation in JUnit 5 is used to register extensions that can customize the behavior of test methods and test classes. This guide covers the basics of using the @ExtendWith annotation to integrate extensions into your tests.
Table of Contents
- Introduction
- Steps to Use
@ExtendWith - Real-World Use Case
- Conclusion
Introduction
The @ExtendWith annotation allows you to register extensions, which are classes that implement the Extension interface. Extensions can be used to customize the behavior of test methods and classes, such as providing parameter injection, lifecycle callbacks, and more.
Steps to Use @ExtendWith
Step 1: Add Maven Dependency
To use JUnit in your project, you need to add the JUnit dependency to your pom.xml file. Use the latest version of JUnit 5:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
Step 2: Create the Class to be Tested
Create a Java class with methods that you want to test. For example, a simple Calculator class:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
}
Step 3: Create a Custom Extension
Create a custom extension by implementing the Extension interface or one of its sub-interfaces. For example, a simple extension that logs the start and end of each test method:
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class LoggingExtension implements BeforeEachCallback, AfterEachCallback {
@Override
public void beforeEach(ExtensionContext context) throws Exception {
System.out.println("Starting test: " + context.getDisplayName());
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
System.out.println("Finished test: " + context.getDisplayName());
}
}
Step 4: Create the Test Class with @ExtendWith
Create a test class in the src/test/java directory. Use the @ExtendWith annotation to register the custom extension.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(LoggingExtension.class)
public class CalculatorTest {
private final Calculator calculator = new Calculator();
@Test
void testAddition() {
assertEquals(5, calculator.add(2, 3), "2 + 3 should equal 5");
}
@Test
void testSubtraction() {
assertEquals(1, calculator.subtract(3, 2), "3 - 2 should equal 1");
}
}
In this example, the CalculatorTest class is annotated with @ExtendWith(LoggingExtension.class), which registers the LoggingExtension. This extension logs the start and end of each test method.
Step 5: Run the Test
You can run the test using your IDE, Maven, or Gradle.
Using an IDE:
Most IDEs, like IntelliJ IDEA and Eclipse, have built-in support for running JUnit tests. Simply right-click on your test class or method and select “Run.”
Using Maven:
If you’re using Maven, you can run your tests with the following command:
mvn test
Using Gradle:
If you’re using Gradle, you can run your tests with the following command:
gradle test
Real-World Use Case
In real-world applications, extensions can be used to integrate external resources, such as databases or web servers, into your tests. For example, a BookService class might need to connect to a database for its tests. An extension can be used to manage the database connection lifecycle.
Create the Class to be Tested
Create a Java class that contains business logic. For example, a BookService class:
public class BookService {
private Map<String, String> books = new HashMap<>();
public boolean addBook(String isbn, String title) {
if (books.containsKey(isbn)) {
return false;
}
books.put(isbn, title);
return true;
}
public boolean updateBook(String isbn, String title) {
if (!books.containsKey(isbn)) {
return false;
}
books.put(isbn, title);
return true;
}
public boolean deleteBook(String isbn) {
if (!books.containsKey(isbn)) {
return false;
}
books.remove(isbn);
return true;
}
public String getBook(String isbn) {
return books.get(isbn);
}
}
Create the Database Extension
Create a custom extension that manages the database connection lifecycle. For example, an extension that sets up and tears down an in-memory database:
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class DatabaseExtension implements BeforeAllCallback, AfterAllCallback {
@Override
public void beforeAll(ExtensionContext context) throws Exception {
// Set up in-memory database
System.out.println("Setting up in-memory database");
}
@Override
public void afterAll(ExtensionContext context) throws Exception {
// Tear down in-memory database
System.out.println("Tearing down in-memory database");
}
}
Create the Test Class with @ExtendWith
Create a test class for the BookService in the src/test/java directory. Use the @ExtendWith annotation to register the custom extension.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(DatabaseExtension.class)
public class BookServiceTest {
private final BookService bookService = new BookService();
@Test
void testAddBook() {
assertTrue(bookService.addBook("123456", "JUnit 5 Guide"), "Book should be added successfully");
}
@Test
void testAddDuplicateBook() {
bookService.addBook("123456", "JUnit 5 Guide");
assertFalse(bookService.addBook("123456", "JUnit 5 Guide"), "Duplicate book should not be added");
}
@Test
void testUpdateBook() {
bookService.addBook("123456", "JUnit 5 Guide");
assertTrue(bookService.updateBook("123456", "JUnit 5 Advanced Guide"), "Book should be updated successfully");
}
@Test
void testDeleteBook() {
bookService.addBook("123456", "JUnit 5 Guide");
assertTrue(bookService.deleteBook("123456"), "Book should be deleted successfully");
}
@Test
void testGetBook() {
bookService.addBook("123456", "JUnit 5 Guide");
assertEquals("JUnit 5 Guide", bookService.getBook("123456"), "Retrieved book should match the added book");
}
}
In this example, the BookServiceTest class is annotated with @ExtendWith(DatabaseExtension.class), which registers the DatabaseExtension. This extension manages the in-memory database setup and teardown.
Running the Tests
You can run the tests using your IDE, Maven, or Gradle.
Using an IDE:
Most IDEs, like IntelliJ IDEA and Eclipse, have built-in support for running JUnit tests. Simply right-click on your test class or method and select “Run.”
Using Maven:
If you’re using Maven, you can run your tests with the following command:
mvn test
Using Gradle:
If you’re using Gradle, you can run your tests with the following command:
gradle test
Conclusion
The @ExtendWith annotation in JUnit makes it easy to register extensions that customize the behavior of your test methods and classes. By using @ExtendWith, you can integrate external resources, manage test lifecycle events, and more. Understanding and using the @ExtendWith annotation effectively is crucial for developing robust and maintainable Java applications.