As your test suite grows, you will want the ability to run different subsets of tests.
Maybe you want to run only the fast tests during development, or skip the integration
tests in your CI pipeline. JUnit 5's @Tag annotation lets you do exactly that.
Basic Tagging
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
class UserServiceTest {
@Test
@Tag("fast")
void shouldValidateEmail() {
// Quick test, no I/O
}
@Test
@Tag("fast")
void shouldFormatName() {
// Quick test, no I/O
}
@Test
@Tag("database")
void shouldPersistUser() {
// Needs database connection
}
@Test
@Tag("integration")
@Tag("slow")
void shouldCallExternalApi() {
// Depends on external service
}
@Test
@Tag("database")
@Tag("integration")
void shouldQueryDatabaseAndCallApi() {
// Both database and external service
}
}
Running Tags from Command Line
# Run only fast tests
mvn test -Dgroups=fast
gradle test -Dgroups=fast
# Run database tests only
mvn test -Dgroups=database
# Run multiple tag groups (OR logic)
mvn test -Dgroups="fast | database"
# Exclude specific tags
mvn test -DexcludedGroups=slow
mvn test -DexcludedGroups="integration | database"
# Combine include and exclude
mvn test -Dgroups=fast -DexcludedGroups=database
Tag Inheritance
Tags are inherited when using @Nested:
@Tag("user-management")
class UserTest {
@Nested
class Registration {
@Test
void test1() {
// Inherits "user-management" tag
}
@Test
@Tag("email")
void test2() {
// Has both "user-management" AND "email" tags
}
}
}
Tag Naming Conventions
There is no standard, but here are common conventions:
// By speed
@Tag("fast")
@Tag("medium")
@Tag("slow")
// By type
@Tag("unit")
@Tag("integration")
@Tag("smoke")
// By feature area
@Tag("authentication")
@Tag("payment")
@Tag("notifications")
// By environment
@Tag("requires-database")
@Tag("requires-network")
@Tag("requires-external-api")
Practical Example
class OrderServiceTest {
@Test
@Tag("unit")
@Tag("fast")
void shouldCalculateTotal() {
OrderService service = new OrderService();
Order order = new Order(List.of(
new Item("Widget", 9.99, 2)
));
assertEquals(19.98, service.calculateTotal(order));
}
@Test
@Tag("unit")
@Tag("fast")
void shouldApplyDiscount() {
OrderService service = new OrderService();
Order order = new Order(List.of(
new Item("Widget", 100.00, 1)
));
service.applyDiscount(order, 10);
assertEquals(90.00, order.getTotal());
}
@Test
@Tag("integration")
@Tag("slow")
@Tag("requires-database")
void shouldPersistOrder() {
// Tests actual database interaction
}
@Test
@Tag("integration")
@Tag("slow")
@Tag("requires-network")
void shouldProcessPayment() {
// Tests actual payment gateway
}
}
During local development, you run mvn test -Dgroups=fast for quick feedback.
In CI, you run all tests. Before deployment, you run -Dgroups=smoke for
critical path tests only.
Programmatic Tag Configuration
You can also configure tag filtering programmatically using a
LauncherDiscoveryRequest or via IDE settings. Most IDEs (IntelliJ,
Eclipse) have a "Tag" filter in the test runner configuration.