Labs ICT
โญ Pro Login

@Tag for Filtering Tests

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.

๐Ÿงช Quick Quiz

How do you filter tests by tag in JUnit 5?