JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck.
Categories is an experimental feature for grouping and filtering tests using marker interfaces. It allows selective test execution based on category annotations, useful for organizing tests by type (unit, integration, performance) or other criteria.
Marks tests or test classes as belonging to one or more categories. Categories are defined as marker interfaces.
/**
* Marks a test method or class as belonging to categories
* @param value - Category interface(s) this test belongs to
*/
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@ValidateWith(CategoryValidator.class)
public @interface Category {
Class<?>[] value();
}Usage Examples:
import org.junit.Test;
import org.junit.experimental.categories.Category;
import static org.junit.Assert.*;
// Define category marker interfaces
public interface SlowTests {}
public interface FastTests {}
public interface IntegrationTests {}
public interface UnitTests {}
// Categorize entire test class
@Category(UnitTests.class)
public class CalculatorTest {
@Test
public void testAddition() {
assertEquals(5, 2 + 3);
}
@Test
@Category(SlowTests.class)
public void testComplexCalculation() {
// This test is both UnitTests and SlowTests
performExpensiveCalculation();
}
}
// Multiple categories
@Category({IntegrationTests.class, SlowTests.class})
public class DatabaseTest {
@Test
public void testConnection() {
// Integration and slow test
connectToDatabase();
}
@Test
@Category(FastTests.class)
public void testConfiguration() {
// Override: this is fast despite class being slow
checkConfig();
}
}
// Method-level categories
public class MixedTest {
@Test
@Category(FastTests.class)
public void fastTest() {
assertTrue(true);
}
@Test
@Category(SlowTests.class)
public void slowTest() {
Thread.sleep(1000);
}
@Test
@Category({IntegrationTests.class, SlowTests.class})
public void integrationTest() {
testExternalSystem();
}
}Special suite runner that filters tests based on included and excluded categories.
/**
* Suite runner that filters tests by category
* Only runs tests matching category criteria
*/
@RunWith(Suite.class)
public class Categories extends Suite {
/**
* Creates Categories runner
* @param klass - Suite class
* @param builder - Runner builder
* @throws InitializationError if initialization fails
*/
public Categories(Class<?> klass, RunnerBuilder builder) throws InitializationError;
/**
* Specify categories to include
* Only tests with these categories will run
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IncludeCategory {
/**
* Categories to include
*/
Class<?>[] value() default {};
/**
* If true, runs tests annotated with any of the categories
* Otherwise, runs tests only if annotated with all categories
*/
boolean matchAny() default true;
}
/**
* Specify categories to exclude
* Tests with these categories will not run
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ExcludeCategory {
/**
* Categories to exclude
*/
Class<?>[] value() default {};
/**
* If true, excludes tests annotated with any of the categories
* Otherwise, excludes tests only if annotated with all categories
*/
boolean matchAny() default true;
}
}Usage Examples:
import org.junit.experimental.categories.Categories;
import org.junit.experimental.categories.Categories.IncludeCategory;
import org.junit.experimental.categories.Categories.ExcludeCategory;
import org.junit.runner.RunWith;
import org.junit.runners.Suite.SuiteClasses;
// Run only fast tests
@RunWith(Categories.class)
@IncludeCategory(FastTests.class)
@SuiteClasses({
CalculatorTest.class,
StringUtilsTest.class,
DateUtilsTest.class,
DatabaseTest.class
})
public class FastTestSuite {
// Only tests marked with @Category(FastTests.class) will run
}
// Run all except slow tests
@RunWith(Categories.class)
@ExcludeCategory(SlowTests.class)
@SuiteClasses({
CalculatorTest.class,
DatabaseTest.class,
NetworkTest.class
})
public class QuickTestSuite {
// All tests except those marked @Category(SlowTests.class)
}
// Run integration tests only
@RunWith(Categories.class)
@IncludeCategory(IntegrationTests.class)
@SuiteClasses({
DatabaseTest.class,
ApiTest.class,
NetworkTest.class,
FileSystemTest.class
})
public class IntegrationTestSuite {
}
// Include and exclude together
@RunWith(Categories.class)
@IncludeCategory(IntegrationTests.class)
@ExcludeCategory(SlowTests.class)
@SuiteClasses({
DatabaseTest.class,
ApiTest.class,
NetworkTest.class
})
public class FastIntegrationSuite {
// Only fast integration tests
}Programmatic filtering of tests by category. Used for custom test execution.
/**
* Filter for selecting tests by category
*/
public class CategoryFilter extends Filter {
/**
* Create filter that includes only matching categories
* @param categoryType - Category to include
* @return CategoryFilter
*/
public static CategoryFilter include(Class<?> categoryType);
/**
* Create filter that includes only matching categories
* @param includes - Categories to include
* @return CategoryFilter
*/
public static CategoryFilter include(Class<?>... includes);
/**
* Create filter that excludes matching categories
* @param categoryType - Category to exclude
* @return CategoryFilter
*/
public static CategoryFilter exclude(Class<?> categoryType);
/**
* Create filter that excludes matching categories
* @param excludes - Categories to exclude
* @return CategoryFilter
*/
public static CategoryFilter exclude(Class<?>... excludes);
/**
* Create filter with include and exclude rules
* @param matchAnyInclusions - Whether to match any (true) or all (false) inclusion categories
* @param inclusions - Set of categories to include
* @param matchAnyExclusions - Whether to match any (true) or all (false) exclusion categories
* @param exclusions - Set of categories to exclude
* @return CategoryFilter
*/
public static CategoryFilter categoryFilter(
boolean matchAnyInclusions,
Set<Class<?>> inclusions,
boolean matchAnyExclusions,
Set<Class<?>> exclusions
);
/**
* Check if test should run
* @param description - Test description
* @return true if test should run
*/
public boolean shouldRun(Description description);
/**
* Get filter description
* @return Description string
*/
public String describe();
}Usage Examples:
import org.junit.experimental.categories.CategoryFilter;
import org.junit.runner.JUnitCore;
import org.junit.runner.Request;
import org.junit.runner.Result;
import java.util.Set;
import java.util.HashSet;
public class CategoryRunner {
public static void main(String[] args) {
// Run only fast tests
Request request = Request.aClass(AllTests.class);
request = request.filterWith(CategoryFilter.include(FastTests.class));
Result result = new JUnitCore().run(request);
// Run all except slow tests
Request request2 = Request.aClass(AllTests.class);
request2 = request2.filterWith(CategoryFilter.exclude(SlowTests.class));
Result result2 = new JUnitCore().run(request2);
// Complex filtering with categoryFilter
Set<Class<?>> inclusions = new HashSet<>();
inclusions.add(IntegrationTests.class);
Set<Class<?>> exclusions = new HashSet<>();
exclusions.add(SlowTests.class);
CategoryFilter filter = CategoryFilter.categoryFilter(
true, // Match any inclusion category
inclusions, // Include integration tests
true, // Match any exclusion category
exclusions // Exclude slow tests
);
Request request3 = Request.aClass(AllTests.class);
request3 = request3.filterWith(filter);
Result result3 = new JUnitCore().run(request3);
}
}
// Custom test runner with categories
public class CustomCategoryRunner {
public static Result runCategories(Class<?> testClass, Class<?>... categories) {
Request request = Request.aClass(testClass);
request = request.filterWith(CategoryFilter.include(categories));
return new JUnitCore().run(request);
}
public static void main(String[] args) {
// Run specific categories
Result result = runCategories(
MyTestSuite.class,
FastTests.class,
UnitTests.class
);
System.out.println("Tests run: " + result.getRunCount());
}
}Organize tests by their nature and scope.
// Marker interfaces for test types
public interface UnitTests {}
public interface IntegrationTests {}
public interface EndToEndTests {}
public interface PerformanceTests {}
public interface SecurityTests {}
@Category(UnitTests.class)
public class BusinessLogicTest {
@Test
public void testCalculation() {
// Pure unit test
}
}
@Category(IntegrationTests.class)
public class DatabaseIntegrationTest {
@Test
public void testDatabaseQuery() {
// Tests with database
}
}
@Category(EndToEndTests.class)
public class UserFlowTest {
@Test
public void testCompleteUserJourney() {
// Full system test
}
}Group tests by execution time for quick feedback loops.
public interface FastTests {} // < 100ms
public interface MediumTests {} // 100ms - 1s
public interface SlowTests {} // > 1s
@Category(FastTests.class)
public class QuickUnitTest {
@Test
public void instantTest() {
assertEquals(4, 2 + 2);
}
}
@Category(SlowTests.class)
public class HeavyIntegrationTest {
@Test
public void testLargeDataset() {
processMillionRecords();
}
}Tests that require specific environments or resources.
public interface RequiresDatabase {}
public interface RequiresNetwork {}
public interface RequiresDocker {}
public interface RequiresLinux {}
public interface RequiresWindows {}
@Category(RequiresDatabase.class)
public class DatabaseTest {
@Test
public void testQuery() {
// Needs database
}
}
@Category({RequiresNetwork.class, RequiresDocker.class})
public class MicroserviceTest {
@Test
public void testServiceCommunication() {
// Needs network and Docker
}
}Organize by feature or module being tested.
public interface PaymentTests {}
public interface AuthenticationTests {}
public interface ReportingTests {}
public interface NotificationTests {}
@Category(PaymentTests.class)
public class PaymentProcessorTest {
@Test
public void testPayment() {
processPayment();
}
}
@Category(AuthenticationTests.class)
public class LoginTest {
@Test
public void testLogin() {
authenticateUser();
}
}Different category suites for different build stages.
// CI pipeline categories
public interface CommitTests {} // Run on every commit
public interface NightlyTests {} // Run nightly
public interface WeeklyTests {} // Run weekly
public interface ManualTests {} // Run manually only
// Commit stage - fast tests only
@RunWith(Categories.class)
@IncludeCategory(CommitTests.class)
@SuiteClasses({/* all test classes */})
public class CommitStageSuite {}
// Nightly build - all automated tests
@RunWith(Categories.class)
@ExcludeCategory(ManualTests.class)
@SuiteClasses({/* all test classes */})
public class NightlyBuildSuite {}
// Usage in tests
@Category(CommitTests.class)
public class FastCoreTest {
// Runs on every commit
}
@Category({NightlyTests.class, SlowTests.class})
public class ExtensiveTest {
// Runs in nightly builds
}Run specific categories from Maven command line.
<!-- pom.xml configuration -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<groups>com.example.FastTests</groups>
<excludedGroups>com.example.SlowTests</excludedGroups>
</configuration>
</plugin>
</plugins>
</build># Run specific categories from command line
mvn test -Dgroups=com.example.FastTests
mvn test -DexcludedGroups=com.example.SlowTests
mvn test -Dgroups=com.example.UnitTests,com.example.FastTestsRun specific categories from Gradle.
// build.gradle configuration
test {
useJUnit {
includeCategories 'com.example.FastTests'
excludeCategories 'com.example.SlowTests'
}
}
// Custom test tasks for different categories
task fastTests(type: Test) {
useJUnit {
includeCategories 'com.example.FastTests'
}
}
task integrationTests(type: Test) {
useJUnit {
includeCategories 'com.example.IntegrationTests'
}
}# Run specific test tasks
./gradlew fastTests
./gradlew integrationTests
./gradlew test # All tests/**
* Base class for test filters
*/
public abstract class Filter {
/**
* Returns true if test should run
* @param description - Test description
* @return true to run test
*/
public abstract boolean shouldRun(Description description);
/**
* Returns description of filter
* @return Description string
*/
public abstract String describe();
/**
* Compose filters with AND logic
* @param second - Second filter
* @return Combined filter
*/
public Filter intersect(Filter second);
/**
* Create filter that matches anything
* @return Filter that allows all tests
*/
public static Filter matchAll();
}
/**
* Factory for creating category filters from command line arguments
*/
public class CategoryFilterFactory implements FilterFactory {
/**
* Create filter from factory parameters
* @param params - Filter parameters
* @return CategoryFilter
*/
public Filter createFilter(FilterFactoryParams params) throws FilterNotCreatedException;
}
/**
* Validates Category annotations
*/
public class CategoryValidator implements AnnotationValidator {
/**
* Validate category annotations on test class
* @param testClass - Test class to validate
* @return List of validation errors
*/
public List<Exception> validateAnnotatedClass(TestClass testClass);
}Install with Tessl CLI
npx tessl i tessl/maven-junit--junit