How do I use assumptions in JUnit tests?

In JUnit 5, assumptions let you run a test only when certain conditions are true. If an assumption fails, the test is skipped/aborted, not failed.

They are useful when a test depends on things like:

  • operating system
  • environment variables
  • external services
  • database availability
  • specific Java version
  • local developer setup

JUnit assumptions are available from:

import static org.junit.jupiter.api.Assumptions.*;

Basic Example

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;

class EnvironmentTest {

    @Test
    void testOnlyRunsOnCiServer() {
        assumeTrue("true".equals(System.getenv("CI")));

        int result = 2 + 3;

        assertEquals(5, result);
    }
}

If the environment variable CI is not set to "true", this test is skipped.

It does not fail.

Using assumeTrue()

assumeTrue() allows the test to continue only if the condition is true.

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assertions.assertTrue;

class OperatingSystemTest {

    @Test
    void testOnlyOnLinux() {
        assumeTrue(System.getProperty("os.name").toLowerCase().contains("linux"));

        assertTrue(true);
    }
}

This test only runs on Linux.

Using assumeFalse()

assumeFalse() is the opposite. The test continues only if the condition is false.

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assumptions.assumeFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;

class LocalEnvironmentTest {

    @Test
    void testDoesNotRunInProduction() {
        assumeFalse("prod".equals(System.getenv("APP_ENV")));

        assertEquals(4, 2 + 2);
    }
}

If APP_ENV is "prod", the test is skipped.

Adding a Message

You can add a message to explain why the test was skipped.

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;

class JavaVersionTest {

    @Test
    void testOnlyOnSpecificJavaVersion() {
        assumeTrue(
                System.getProperty("java.version").startsWith("25"),
                "This test only runs on Java 25"
        );

        assertEquals(10, 5 + 5);
    }
}

The message helps explain the skipped test in the test report.

Using assumingThat()

assumingThat() lets you run only part of a test conditionally.

Unlike assumeTrue(), it does not skip the whole test. It only skips the block of code inside it.

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assumptions.assumingThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class ConditionalBlockTest {

    @Test
    void testWithConditionalSection() {
        assertEquals(4, 2 + 2);

        assumingThat(
                "dev".equals(System.getenv("APP_ENV")),
                () -> {
                    assertTrue(true);
                    System.out.println("Extra checks for development environment");
                }
        );

        assertEquals(6, 3 + 3);
    }
}

In this example:

  • the first assertion always runs
  • the conditional block runs only when APP_ENV is "dev"
  • the last assertion always runs

Difference Between Assertions and Assumptions

Feature Assertion Assumption
Purpose Verify expected behavior Check whether test should run
If condition fails Test fails Test is skipped
Common methods assertEquals(), assertTrue() assumeTrue(), assumeFalse()
Used for Validating code correctness Checking test prerequisites

Example: Skipping Test If Database Is Not Available

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;

class DatabaseTest {

    @Test
    void testDatabaseQuery() {
        assumeTrue(isDatabaseAvailable(), "Database is not available");

        String result = "John";

        assertEquals("John", result);
    }

    private boolean isDatabaseAvailable() {
        // In a real test, you might check a connection here.
        return false;
    }
}

Since isDatabaseAvailable() returns false, the test is skipped.

Common Assumption Methods

JUnit 5 provides these commonly used methods:

assumeTrue(condition);
assumeTrue(condition, message);

assumeFalse(condition);
assumeFalse(condition, message);

assumingThat(condition, executable);

When Should You Use Assumptions?

Use assumptions when the test depends on something outside the code being tested.

Good examples:

assumeTrue(System.getenv("CI") != null);
assumeTrue(System.getProperty("os.name").contains("Linux"));
assumeFalse("prod".equals(System.getenv("APP_ENV")));

Avoid using assumptions to hide broken tests. If the code is wrong, use assertions and let the test fail.

Summary

Assumptions in JUnit are used to skip tests when required conditions are not met.

Use:

assumeTrue(condition);

when a test should continue only if a condition is true.

Use:

assumeFalse(condition);

when a test should continue only if a condition is false.

Use:

assumingThat(condition, () -> {
    // conditional checks
});

when only part of a test should run conditionally.

How do I write repeated tests in JUnit?

In JUnit 5, repeated tests are written using the @RepeatedTest annotation.

A repeated test runs the same test method multiple times without requiring different input values.

Basic Example

import org.junit.jupiter.api.RepeatedTest;

import static org.junit.jupiter.api.Assertions.assertTrue;

class RandomNumberTest {

    @RepeatedTest(5)
    void randomNumberShouldBeLessThanTen() {
        int number = (int) (Math.random() * 10);

        assertTrue(number >= 0 && number < 10);
    }
}

This test runs 5 times.

Difference Between @Test and @RepeatedTest

Instead of writing:

import org.junit.jupiter.api.Test;

class ExampleTest {

    @Test
    void shouldRunOnce() {
        // test logic
    }
}

You write:

import org.junit.jupiter.api.RepeatedTest;

class ExampleTest {

    @RepeatedTest(3)
    void shouldRunThreeTimes() {
        // test logic
    }
}

Access the Current Repetition

JUnit can inject a RepetitionInfo parameter into a repeated test. This lets you know which repetition is currently running.

import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;

import static org.junit.jupiter.api.Assertions.assertTrue;

class RetryExampleTest {

    @RepeatedTest(5)
    void repeatedTestWithInfo(RepetitionInfo repetitionInfo) {
        int current = repetitionInfo.getCurrentRepetition();
        int total = repetitionInfo.getTotalRepetitions();

        System.out.println("Running repetition " + current + " of " + total);

        assertTrue(current >= 1);
        assertTrue(current <= total);
    }
}

Example output:

Running repetition 1 of 5
Running repetition 2 of 5
Running repetition 3 of 5
Running repetition 4 of 5
Running repetition 5 of 5

Customize the Display Name

You can customize the name shown for each repeated test invocation.

import org.junit.jupiter.api.RepeatedTest;

class LoginTest {

    @RepeatedTest(value = 3, name = "Login attempt {currentRepetition} of {totalRepetitions}")
    void loginShouldSucceedRepeatedly() {
        // test login logic
    }
}

This may display as:

Login attempt 1 of 3
Login attempt 2 of 3
Login attempt 3 of 3

Common placeholders are:

{displayName}
{currentRepetition}
{totalRepetitions}

JUnit also provides predefined formats:

@RepeatedTest(value = 3, name = RepeatedTest.SHORT_DISPLAY_NAME)

or:

@RepeatedTest(value = 3, name = RepeatedTest.LONG_DISPLAY_NAME)

Example with @BeforeEach

Lifecycle methods such as @BeforeEach run before each repetition.

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.RepeatedTest;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CounterTest {

    private int counter;

    @BeforeEach
    void setUp() {
        counter = 0;
    }

    @RepeatedTest(3)
    void counterStartsAtZeroEachTime() {
        assertEquals(0, counter);

        counter++;
    }
}

Because @BeforeEach runs before every repetition, the counter starts at 0 each time.

When to Use Repeated Tests

Use @RepeatedTest when you want to run the same test multiple times, for example:

  • testing code that uses random values
  • checking for flaky behavior
  • verifying repeated operations
  • testing concurrency-related code
  • making sure state is reset between runs

@RepeatedTest vs Parameterized Tests

Use @RepeatedTest when the test logic is the same and the input does not change.

Use parameterized tests when you want to run the same test with different inputs.

@RepeatedTest(5)
void sameTestRepeatedSeveralTimes() {
    // same input or generated input each time
}
@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void sameTestWithDifferentValues(int value) {
    // runs once for each value
}

Maven Dependency

Make sure JUnit Jupiter is available in your project:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.13.4</version>
    <scope>test</scope>
</dependency>

Gradle Dependency

testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'

test {
    useJUnitPlatform()
}

Summary

Use @RepeatedTest to run the same test multiple times:

import org.junit.jupiter.api.RepeatedTest;

class ExampleTest {

    @RepeatedTest(5)
    void shouldRunFiveTimes() {
        // test logic
    }
}

A repeated test is useful when you need the same test executed more than once, while a parameterized test is better when each run needs different input data.

How do I run only selected JUnit tests by tag?

To run only selected JUnit 5 tests by tag, mark your tests with @Tag, then configure your build tool or IDE to include only that tag.

1. Add tags to your tests

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

class PaymentServiceTest {

    @Test
    @Tag("fast")
    void calculatesTotal() {
        // test code
    }

    @Test
    @Tag("integration")
    void connectsToPaymentGateway() {
        // test code
    }
}

You can also tag an entire test class:

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Tag("integration")
class UserRepositoryTest {

    @Test
    void savesUser() {
        // test code
    }
}

All tests in that class inherit the integration tag.


2. Run tagged tests with Maven

If you use Maven Surefire, run tests with a specific tag like this:

mvn test -Dgroups=integration

To run multiple tags:

mvn test -Dgroups="fast,integration"

To exclude a tag:

mvn test -DexcludedGroups=slow

Example Maven configuration:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.5.2</version>
        </plugin>
    </plugins>
</build>

3. Run tagged tests with Gradle

For Gradle, configure the test task:

tasks.test {
    useJUnitPlatform {
        includeTags 'integration'
    }
}

Then run:

gradle test

You can include multiple tags:

tasks.test {
    useJUnitPlatform {
        includeTags 'fast', 'integration'
    }
}

Or exclude tags:

tasks.test {
    useJUnitPlatform {
        excludeTags 'slow'
    }
}

You can also pass tags from the command line:

tasks.test {
    useJUnitPlatform {
        if (project.hasProperty('includeTags')) {
            includeTags project.property('includeTags').split(',')
        }
    }
}

Then run:

gradle test -PincludeTags=integration

4. Run tagged tests in IntelliJ IDEA

In IntelliJ IDEA:

  1. Open Run | Edit Configurations…
  2. Create or select a JUnit run configuration.
  3. Set Test kind to Tags.
  4. Enter the tag name, for example:
    integration
    
  5. Run the configuration.

    You can use tag expressions such as:

    fast & !slow
    

    or:

    integration | smoke
    

Summary

Tool Example
JUnit annotation @Tag("integration")
Maven mvn test -Dgroups=integration
Gradle includeTags 'integration'
IntelliJ IDEA JUnit run configuration → Test kind: Tags

Use tags like fast, slow, unit, integration, or smoke to organize your test suite and run only the tests you need.

How do I use tags to group JUnit tests?

In JUnit 5, you can use the @Tag annotation to group tests into categories such as:

  • fast
  • slow
  • unit
  • integration
  • database
  • api
  • smoke

Tags are useful when you want to run only certain groups of tests, for example only fast unit tests during development, or only integration tests in a CI pipeline.


1. Basic Example

Use @Tag on a test method:

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    @Test
    @Tag("fast")
    void shouldAddTwoNumbers() {
        Calculator calculator = new Calculator();

        int result = calculator.add(2, 3);

        assertEquals(5, result);
    }

    @Test
    @Tag("slow")
    void shouldCalculateLargeDataSet() {
        Calculator calculator = new Calculator();

        int result = calculator.processLargeDataSet();

        assertEquals(1000, result);
    }
}

In this example:

  • shouldAddTwoNumbers() belongs to the fast group.
  • shouldCalculateLargeDataSet() belongs to the slow group.

2. Tag an Entire Test Class

You can place @Tag on a class to apply the tag to all tests inside it.

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertTrue;

@Tag("unit")
class StringUtilsTest {

    @Test
    void shouldReturnTrueForBlankString() {
        assertTrue(StringUtils.isBlank(""));
    }

    @Test
    void shouldReturnTrueForWhitespaceString() {
        assertTrue(StringUtils.isBlank("   "));
    }
}

All tests in StringUtilsTest are now tagged as unit.


3. Use Multiple Tags

A test can have more than one tag.

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertNotNull;

class UserServiceTest {

    @Test
    @Tag("unit")
    @Tag("fast")
    void shouldCreateUser() {
        UserService userService = new UserService();

        User user = userService.createUser("[email protected]");

        assertNotNull(user);
    }

    @Test
    @Tag("integration")
    @Tag("database")
    void shouldSaveUserToDatabase() {
        UserRepository userRepository = new UserRepository();

        User user = userRepository.save(new User("[email protected]"));

        assertNotNull(user.getId());
    }
}

This allows you to run tests by different categories.

For example:

  • Run all unit tests.
  • Run all fast tests.
  • Run all integration tests.
  • Exclude all database tests.

4. Run Tagged Tests with Maven

If you are using Maven Surefire, you can run tests with a specific tag like this:

mvn test -Dgroups=unit

To run multiple tags:

mvn test -Dgroups=unit,fast

To exclude a tag:

mvn test -DexcludedGroups=slow

Example:

mvn test -Dgroups=integration -DexcludedGroups=slow

This runs tests tagged with integration, but excludes tests tagged with slow.


5. Configure Tags in pom.xml

You can also configure Maven to include or exclude tags permanently.

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.5.2</version>
            <configuration>
                <groups>unit</groups>
                <excludedGroups>slow</excludedGroups>
            </configuration>
        </plugin>
    </plugins>
</build>

This configuration runs tests tagged unit and excludes tests tagged slow.


6. Run Tagged Tests with Gradle

If you are using Gradle, configure the test task:

test {
    useJUnitPlatform {
        includeTags 'unit'
        excludeTags 'slow'
    }
}

Then run:

gradle test

You can also create separate test tasks:

tasks.register('integrationTest', Test) {
    useJUnitPlatform {
        includeTags 'integration'
    }
}

Run it with:

gradle integrationTest

7. Tag Integration Tests

A common use case is separating unit tests from integration tests.

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertTrue;

@Tag("integration")
class UserRepositoryTest {

    @Test
    void shouldConnectToDatabase() {
        boolean connected = true;

        assertTrue(connected);
    }
}

Then you can run only integration tests:

mvn test -Dgroups=integration

Or exclude them during regular builds:

mvn test -DexcludedGroups=integration

8. Tag Nested Tests

Tags also work with nested test classes.

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

class OrderServiceTest {

    @Nested
    @Tag("validation")
    class ValidationTests {

        @Test
        void shouldRejectInvalidOrder() {
            // validation test
        }

        @Test
        void shouldAcceptValidOrder() {
            // validation test
        }
    }

    @Nested
    @Tag("pricing")
    class PricingTests {

        @Test
        void shouldCalculateDiscount() {
            // pricing test
        }

        @Test
        void shouldApplyTax() {
            // pricing test
        }
    }
}

Here:

  • All tests in ValidationTests are tagged validation.
  • All tests in PricingTests are tagged pricing.

9. Tag Naming Rules

JUnit tag names must follow a few rules:

  • A tag must not be blank.
  • A tag must not contain whitespace.
  • A tag must not contain ISO control characters.
  • A tag must not contain reserved characters:
    • ,
    • (
    • )
    • &
    • |
    • !

Good examples:

@Tag("unit")
@Tag("integration")
@Tag("fast")
@Tag("database")
@Tag("smoke")

Avoid:

@Tag("unit test")
@Tag("fast,unit")
@Tag("integration&database")

10. Best Practices

Use tags for broad execution groups, not for every small detail.

Good tag categories:

  • unit
  • integration
  • slow
  • fast
  • database
  • api
  • smoke

Avoid over-tagging tests with too many labels.

For example, this is usually enough:

@Test
@Tag("integration")
@Tag("database")
void shouldSaveCustomer() {
}

But this is probably too much:

@Test
@Tag("integration")
@Tag("database")
@Tag("repository")
@Tag("customer")
@Tag("save")
@Tag("positive")
void shouldSaveCustomer() {
}

Summary

Use JUnit 5 @Tag to group and filter tests.

@Test
@Tag("fast")
void shouldRunQuickly() {
}

You can apply tags to:

  • Individual test methods
  • Entire test classes
  • Nested test classes

Then run selected groups using Maven or Gradle:

mvn test -Dgroups=fast
test {
    useJUnitPlatform {
        includeTags 'fast'
    }
}

Tags are especially useful for separating unit tests, integration tests, slow tests, and database-dependent tests.

How do I use @EnumSource to test enum values?

@EnumSource is a JUnit 5 parameterized-test source that runs the same test once for each selected enum constant.

Basic usage

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import static org.junit.jupiter.api.Assertions.assertNotNull;

class DirectionTest {

    enum Direction {
        NORTH, SOUTH, EAST, WEST
    }

    @ParameterizedTest
    @EnumSource(Direction.class)
    void shouldTestAllDirections(Direction direction) {
        assertNotNull(direction);
    }
}

This runs the test 4 times: once with NORTH, SOUTH, EAST, and WEST.

Test only specific enum values

Use names to include selected constants:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import static org.junit.jupiter.api.Assertions.assertTrue;

class DirectionTest {

    enum Direction {
        NORTH, SOUTH, EAST, WEST
    }

    @ParameterizedTest
    @EnumSource(value = Direction.class, names = {"NORTH", "EAST"})
    void shouldTestOnlyNorthAndEast(Direction direction) {
        assertTrue(direction == Direction.NORTH || direction == Direction.EAST);
    }
}

Exclude specific enum values

Use mode = EnumSource.Mode.EXCLUDE:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import static org.junit.jupiter.api.Assertions.assertNotEquals;

class DirectionTest {

    enum Direction {
        NORTH, SOUTH, EAST, WEST
    }

    @ParameterizedTest
    @EnumSource(
        value = Direction.class,
        names = {"WEST"},
        mode = EnumSource.Mode.EXCLUDE
    )
    void shouldTestAllExceptWest(Direction direction) {
        assertNotEquals(Direction.WEST, direction);
    }
}

This runs for NORTH, SOUTH, and EAST.

Match enum names with a regex

Use mode = EnumSource.Mode.MATCH_ANY:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import static org.junit.jupiter.api.Assertions.assertTrue;

class DirectionTest {

    enum Direction {
        NORTH, SOUTH, EAST, WEST
    }

    @ParameterizedTest
    @EnumSource(
        value = Direction.class,
        names = {"N.*", "E.*"},
        mode = EnumSource.Mode.MATCH_ANY
    )
    void shouldTestNamesStartingWithNOrE(Direction direction) {
        assertTrue(direction.name().startsWith("N") || direction.name().startsWith("E"));
    }
}

This runs for NORTH and EAST.

Common modes

EnumSource.Mode.INCLUDE     // default; include listed names
EnumSource.Mode.EXCLUDE     // exclude listed names
EnumSource.Mode.MATCH_ANY   // include names matching any regex
EnumSource.Mode.MATCH_ALL   // include names matching all regexes

Typical real-world example

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import static org.junit.jupiter.api.Assertions.assertTrue;

class OrderStatusTest {

    enum OrderStatus {
        NEW, PAID, SHIPPED, CANCELLED
    }

    @ParameterizedTest
    @EnumSource(value = OrderStatus.class, names = {"PAID", "SHIPPED"})
    void completedStatusesShouldBeProcessed(OrderStatus status) {
        assertTrue(status == OrderStatus.PAID || status == OrderStatus.SHIPPED);
    }
}

In short:

@ParameterizedTest
@EnumSource(MyEnum.class)
void test(MyEnum value) {
    // test each enum value
}