---
title: JUnit 4 reference
description: Reference documentation for Allure JUnit 4 integration | How to add steps and additional metadata | Create attachments | Organise tests
---

# Allure JUnit 4 reference

These are the functions that you can use to integrate your JUnit 4 tests with Allure.

Allure JUnit 4 provides more than one way to use some features. In most cases, one way involves using an annotation on the test method, while the other involves a method call inside the test method body. The latter style is called “dynamic” and allows to construct values dynamically.

## Metadata

Assign a test's [description, links and other metadata](/docs/v2/readability/#description-links-and-other-metadata).

### Title

- `@DisplayName(String value)`
- `Allure.getLifecycle().updateTestCase(Consumer<TestResult> update)`

Set the test's [title](/docs/v2/readability/#title).

If you need to construct a test's title dynamically, write a lambda function that does that and pass it to the `updateTestCase()` method of the `AllureLifecycle` object, see the example below.

**Annotations API:**
```java
import io.qameta.allure.junit4.DisplayName;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    @DisplayName("Test Authentication")
    public void testAuthentication() {
        // ...
    }
}
```

**Runtime API:**
```java
import io.qameta.allure.Allure;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    public void testAuthentication() {
        Allure.getLifecycle().updateTestCase(result -> {
            result.setName("Test Authentication");
        });
        // ...
    }
}
```

### Description

- `@Description(String value="")`
- `Allure.description(String description)`

Set the test's [description](/docs/v2/readability/#description).

Use the `@Description()` annotation to set a description statically or use the `description()` method to set it dynamically in runtime.

**Annotations API:**
```java
import io.qameta.allure.Description;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    @Description("This test attempts to log into the website using a login and a password. Fails if any error happens.\n\nNote that this test does not test 2-Factor Authentication.")
    public void testAuthentication() {
        // ...
    }
}
```

**Runtime API:**
```java
import io.qameta.allure.Allure;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    public void testAuthentication() {
        Allure.description("This test attempts to log into the website using a login and a password. Fails if any error happens.\n\nNote that this test does not test 2-Factor Authentication.");
        // ...
    }
}
```

Alternatively, you can let Allure JUnit 4 parse the description from the test method's JavaDoc comment. To do so, first add a special annotation processor to your project's Maven or Gradle configuration.

```kotlin
// In the project's `build.gradle.kts`:
val allureVersion = "2.32.0"

dependencies {
    // ...
    testAnnotationProcessor("io.qameta.allure:allure-descriptions-javadoc:$allureVersion")
}
```

Then, use the Annotations API without arguments to parse the description from JavaDoc. Note that on a large project, processing the annotations may significantly increase both the compilation time and the file size of the test executable files.

Markdown formatting is allowed. Any HTML formatting, if present, will be stripped for security purposes.

```java
import io.qameta.allure.Description;
import org.junit.Test;

public class TestMyWebsite {

    /**
     * This test attempts to log into the website using a login and a password. Fails if any error happens.
    * <p>
    * Note that this test does not test 2-Factor Authentication.
    */
    @Test
    @Description
    public void testAuthentication() {
        // ...
    }
}
```

### Owner

- `@Owner(String value="")`

Set the test's [owner](/docs/v2/readability/#owner).

**Annotations API:**
```java
import io.qameta.allure.Owner;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    @Owner("John Doe")
    public void testAuthentication() {
        // ...
    }
}
```

**Runtime API:**
```java
import io.qameta.allure.Allure;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    public void testAuthentication() {
        Allure.label("owner", "John Doe");
        // ...
    }
}
```

### Tag

- `@Tag(String value)`
- `@Tags(Tag[] value)`

Set the test's [tags](/docs/v2/readability/#tags).

**Annotations API:**
```java
import io.qameta.allure.junit4.Tag;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    @Tag("NewUI")
    @Tag("Essentials")
    @Tag("Authentication")
    public void testAuthentication() {
        // ...
    }
}
```

**Runtime API:**
```java
import io.qameta.allure.Allure;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    public void testAuthentication() {
        Allure.label("tag", "NewUI");
        Allure.label("tag", "Essentials");
        Allure.label("tag", "Authentication");
        // ...
    }
}
```

### Severity

- `@Severity(SeverityLevel value)`

Set the test's [severity](/docs/v2/readability/#severity).

Allowed values are: “trivial”, “minor”, “normal”, “critical”, and “blocker”.

**Annotations API:**
```java
import io.qameta.allure.Severity;
import org.junit.Test;

import static io.qameta.allure.SeverityLevel.*;

public class TestMyWebsite {

    @Test
    @Severity(CRITICAL)
    public void testAuthentication() {
        // ...
    }
}
```

**Runtime API:**
```java
import io.qameta.allure.Allure;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    public void testAuthentication() {
        Allure.label("severity", "critical");
        // ...
    }
}
```

### Label

- `@LabelAnnotation(String name, String value=DEFAULT_VALUE)`
- `Allure.label(String name, String value)`

Set an arbitrary [label](/docs/v2/readability/#other-labels) for the test. This is the underlying implementation for a lot of Allure's other functions.

To set a label dynamically, just call the `label()` function with a name and a value for a label. You can do it multiple times to create an array of values under that name.

Another way of setting a label is to create a custom class using `@LabelAnnotation` (in a separate file) and annotate the tests with the new annotation, see the example below. Please be careful to copy `@Retention`, `@Target` and the other necessary annotations as this is shown in the example, otherwise you custom annotation may not work properly.

**Annotations API:**
```java
import io.qameta.allure.LabelAnnotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@LabelAnnotation(name = "CustomLabelName")
@interface MyLabel {
    String value();
}
```

```java
import org.junit.Test;

public class TestMyWebsite {

    @Test
    @MyLabel("custom label value")
    public void testAuthentication() {
        // ...
    }
}
```

**Runtime API:**
```java
import io.qameta.allure.Allure;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    public void testAuthentication() {
        Allure.label("language", "java");
        Allure.label("framework", "junit4");
        Allure.label("CustomLabelName", "custom label value");
        // ...
    }
}
```

### Allure ID (Allure TestOps)

- `@AllureId(String value)`

Set the test's [ID](/docs/v2/readability/#id).

```java
import io.qameta.allure.AllureId;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    @AllureId("123")
    public void testAuthentication() {
        // ...
    }
}
```

### Link

- `@Link(String value="", name="", String url="", String type=CUSTOM_LINK_TYPE)`
- `@Links(Link[] value)`
- `@Issue(String value="")`
- `@Issues(Issue[] value)`
- `@TmsLink(String value="")`
- `@TmsLinks(TmsLink[] value)`
- `Allure.link(String url)`
- `Allure.link(String name, String url)`
- `Allure.link(String name, String type, String url)`
- `Allure.issue(String name, String url)`
- `Allure.tms(String name, String url)`

Add a [link](/docs/v2/readability/#links) related to the test.

Based on the `type` (which can be any string), Allure will try to load a corresponding **link pattern** to process the URL, as defined by the [`allure.link.*.pattern`](/docs/junit4-configuration/#allure-link-⟨type⟩-pattern) configuration option. If no pattern found for the given type, the URL is left unmodified.

The `name` will be used as the link's text. If it is omitted, the unprocessed URL will be used instead.

For convenience, Allure provides two shorthand functions with pre-selected link types: `issue()` and `tms()`.

**Annotations API:**
```java
import io.qameta.allure.Issue;
import io.qameta.allure.Link;
import io.qameta.allure.TmsLink;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    @Link(name = "Website", url = "https://dev.example.com/")
    @Issue("AUTH-123")
    @TmsLink("TMS-456")
    public void testAuthentication() {
        // ...
    }
}
```

**Runtime API:**
```java
import io.qameta.allure.Allure;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    public void testAuthentication() {
        Allure.link("Website", "https://dev.example.com/");
        Allure.issue("AUTH-123", "https://jira.example.org/browse/AUTH-123");
        Allure.tms("TMS-456", "https://tms.example.org/TMS-456");
        // ...
    }
}
```

## Behavior-based hierarchy

- `@Epic(String value="")`
- `@Epics(Epic[] value)`
- `@Feature(String value="")`
- `@Features(Feature[] value)`
- `@Story(String value="")`
- `@Stories(Story[] value)`
- `Allure.epic(String value)`
- `Allure.feature(String value)`
- `Allure.story(String value)`

Assign names of **epics**, **features** or **user stories** for a test, as part of Allure's [behavior-based hierarchy](/docs/v2/navigation/#behavior-based-hierarchy).

**Annotations API:**
```java
import io.qameta.allure.Epic;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    @Epic("Web interface")
    @Feature("Essential features")
    @Story("Authentication")
    public void testAuthentication() {
        // ...
    }
}
```

**Runtime API:**
```java
import io.qameta.allure.Allure;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    public void testAuthentication() {
        Allure.epic("Web interface");
        Allure.feature("Essential features");
        Allure.story("Authentication");
        // ...
    }
}
```

## Suite-based hierarchy

- `Allure.suite(String value)`

Assign the name of suite, as part of Allure's [suite-based hierarchy](/docs/v2/navigation/#suite-based-hierarchy).

**Runtime API:**
```java
import io.qameta.allure.Allure;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    public void testAuthentication() {
        Allure.label("parentSuite" "Tests for web interface");
        Allure.suite("Tests for essential features");
        Allure.label("subSuite", "Tests for authentication");
        // ...
    }
}
```

## Test steps

- `@Step(String value="")`
- `@Param(String value="", String name="", Parameter.Mode mode=DEFAULT, boolean excluded=false)`
- `Allure.step(String name)`
- `Allure.step(String name, Status status)`
- `Allure.step(String name, ThrowableRunnableVoid runnable)`
- `Allure.step(String name, ThrowableRunnable<T> runnable)`
- `Allure.step(ThrowableContextRunnableVoid<StepContext> runnable)`
- `Allure.step(String name, ThrowableContextRunnableVoid<StepContext> runnable)`
- `Allure.step(String name, ThrowableContextRunnable<T, StepContext> runnable)`
- `Allure.step(ThrowableContextRunnable<T, StepContext> runnable)`

Define a [test step](/docs/steps/) with the given `name`.

There are three ways of defining a step.

- “An annotated step”: define a method containing a test step and decorate it with `@Step()`.
- “A lambda step”: write a test step in a lambda function and pass it to `Allure.step()`.
- “A no-op step”: just pass a `name` and an optional `status` to `Allure.step()` to immediately add a corresponding entry to the results as a sub-step of the current step.

When you are implementing an annotated step that takes arguments, you can specify human-readable names for them via the `@Param` annotation. The arguments will be displayed as step-level parameters under the provided names. For arguments without the `@Param` annotation, Allure JUnit 4 will use either names indicating the arguments' order (“arg1”, “arg2”, etc) or the argument names from the Java code. The latter requires the `-parameters` compiler argument, see [How to start](/docs/junit4/#how-to-start).

When you are implementing a function for a lambda step, it can accept either no arguments or a single argument of class `StepContext`. This object provides the following methods:

- `name()` — override the step name during its execution.
- `parameter()` — indicate arbitrary parameters used for the step.

Both the `@Param` annotation and the step context's `parameter()` method allow for the same additional arguments as the test-wide implementation of the [Parametrized tests](#parametrized-tests).

**Annotated steps:**
```java
import io.qameta.allure.Param;
import io.qameta.allure.Step;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    public void testAuthentication() {
        testAuthenticationWith("https://1.example.com/", "alice", "qwerty");
        testAuthenticationWith("https://2.example.com/", "bob", "asdfgh");
        testAuthenticationWith("https://3.example.com/", "charlie", "zxcvbn");
    }

    @Step("Authenticate on {url}")
    void testAuthenticationWith(String url, String login, String password) {
        openWebPage(url);
        enterCredentials(login, password);
        checkIfLoggedInAs(login);
    }

    @Step("Visit {url}")
    void openWebPage(@Param("URL") String url) {
        // ...
    }

    @Step("Enter credentials")
    void enterCredentials(
            @Param("Login") String login,
            @Param("Password") String password
    ) {
        // ...
    }

    @Step("Check if I am logged in")
    void checkIfLoggedInAs(
            @Param("Login") String login
    ) {
        // ...
    }
}
```

**Lambda steps:**
```java
import io.qameta.allure.Allure;
import org.junit.Test;

public class TestMyWebsite {

    private static final String[][] authenticationData = new String[][]{
            {"https://1.example.com/", "alice", "qwerty"},
            {"https://2.example.com/", "bob", "asdfgh"},
            {"https://3.example.com/", "charlie", "zxcvbn"},
    };

    @Test
    public void testAuthentication() {

        for (String[] data : authenticationData) {
            String url = data[0];
            String login = data[1];
            String password = data[2];

            Allure.step("Authenticate on %s".formatted(url), () -> {

                Allure.step("Visit %s".formatted(url), stepContext -> {
                    stepContext.parameter("URL", url);
                    // ...
                });

                Allure.step("Enter credentials", stepContext -> {
                    stepContext.parameter("Login", login);
                    stepContext.parameter("Password", password);
                    // ...
                });

                Allure.step("Check if I am logged in", stepContext -> {
                    stepContext.parameter("Login", login);
                    // ...
                });
            });
        }
    }
}
```

**No-op steps:**
```java
import io.qameta.allure.Allure;
import io.qameta.allure.model.Status;
import org.junit.Test;

public class TestMyWebsite {

    private static final String[][] authenticationData = new String[][]{
            {"https://1.example.com/", "alice", "qwerty"},
            {"https://2.example.com/", "bob", "asdfgh"},
            {"https://3.example.com/", "charlie", "zxcvbn"},
    };

    @Test
    public void testAuthentication() {

        for (String[] data : authenticationData) {
            String url = data[0];
            String login = data[1];
            String password = data[2];

            Allure.step("Begin testing %s...".formatted(url));
            try {
                // ...
                Allure.step("Visited %s.".formatted(url));

                // ...
                Allure.step("Entered credentials.");

                // ...
                Allure.step("I am logged in!");

            } catch (Exception e) {
                Allure.step("Failed!", Status.FAILED);
            }
        }
    }
}
```

## Parametrized tests

- `Allure.parameter(String name, T value)`
- `Allure.parameter(String name, T value, Boolean excluded)`
- `Allure.parameter(String name, T value, Parameter.Mode mode)`
- `Allure.parameter(String name, T value, Boolean excluded, Parameter.Mode mode)`

Specify a `name` and `value` of a parameter that was used during this test. See [Parametrized tests](/docs/v2/readability/#parametrized-tests) for more details.

Warning:
Allure JUnit 4 identifies tests based only on their names. If you have multiple tests that only differ in parameters, they will be treated as one test for the purposes of [History and retries](/docs/history-and-retries/).

If the `excluded` argument is set to true, Allure will not use the parameter when comparing the current test result with previous one in the history. This argument is only used by Allure TestOps.

The `mode` argument affects how the parameter will be displayed in the report. Available options are defined in the `Parameter.Mode` enumeration:

- `Parameter.Mode.DEFAULT` (same as not specifying any mode) — the parameter and its value will be shown in a table along with other parameters.
- `Parameter.Mode.MASKED` — the parameter will be shown in the table, but its value will be hidden. Use this mode for passwords, tokens and other sensitive parameters.
- `Parameter.Mode.HIDDEN` — the parameter and its value will not be shown in the test report. Note, however, that it is still possible to extract the value from the `allure_results` directory if you publish it.

```java
import io.qameta.allure.Allure;
import org.junit.Test;

public class TestMyWebsite {

    @Test
    public void testAuthenticationWithUsername() {
        Allure.parameter("login", "johndoe");
        // ...
    }

    @Test
    public void testAuthenticationWithEmail() {
        Allure.parameter("login", "johndoe@example.com");
        // ...
    }
}
```

## Attachments

- `@Attachment(String value="", String type="", String fileExtension="")`
- `Allure.addAttachment(String name, String content)`
- `Allure.addAttachment(String name, String type, String content)`
- `Allure.addAttachment(String name, String type, String content, String fileExtension)`
- `Allure.addAttachment(String name, InputStream content)`
- `Allure.addAttachment(String name, String type, InputStream content, String fileExtension)`
- `Allure.attachment(String name, String content)`
- `Allure.attachment(String name, InputStream content)`

Add `content` as an [attachment](/docs/attachments/) to the test result under the given `name` (defaults to a unique pseudo-random string).

Tip:
You can use data produced by any function, not necessarily read from an actual file.

To create an attachment using the Annotations API, define a method that returns some data and annotate it with `@Attachment`. Call the method at any point during your test.

To create an attachment using the Runtime API, just call `addAttachment()` or `attachment()` at any point during your test. Pass the data as the `content` argument.

The data will be processed as following:

- If the data type is `byte[]`, Allure will use it without modification.
- If the data type is `String`, Allure will convert it to `byte[]`.
- If the data is of any other type, Allure will call the `toString()` method and then convert the string to `byte[]`.

To ensure that the reader's web browser will display attachments correctly, it is recommended to specify each attachment's type. To do so, pass the media type of the content as `type` and, optionally, a filename extension as `fileExtension`. The media type affects how the data will be displayed in the test report, while the filename extension is appended to the filename when user wants to save the file.

**Annotations API:**
```java
import io.qameta.allure.Attachment;
import org.junit.Test;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TestMyWebsite {

    @Test
    public void testAuthentication() throws IOException {
        // ...
        attachDataTXT();
        attachScreenshotPNG();
    }

    @Attachment(value = "data", type = "text/plain", fileExtension = ".txt")
    public String attachDataTXT() {
        return "This is the file content.";
    }

    @Attachment(value = "screenshot", type = "image/png", fileExtension = ".png")
    public byte[] attachScreenshotPNG() throws IOException {
        return Files.readAllBytes(Paths.get("/path/to/image.png"));
    }
}
```

**Runtime API:**
```java
import io.qameta.allure.Allure;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TestMyWebsite {

    @Test
    public void testAuthentication() throws IOException {
        // ...
        Allure.attachment("data.txt", "This is the file content.");
        try (InputStream is = Files.newInputStream(Paths.get("/path/img.png"))) {
            Allure.attachment("image.png", is);
        }
    }
}
```

Info:
Additionally, Allure provides a way to spawn threads that will add attachments to the test results asynchronously without blocking the test runner. While not recommended for the majority of cases, this approach can improve tests that otherwise have to wait for processing very large files, such as videos larger than 1 GB. Please refer to the source code for `Allure.addByteAttachmentAsync()` and `Allure.addStreamAttachmentAsync()` to learn more.

## Results history

### Flaky

- `@Flaky`

Indicate that the test is known to be unstable and can may not succeed every time. See [Flaky tests](/docs/test-stability/#flaky-tests).
