Skip to content

How to Test Vaadin 8 Components in Isolation with TestBench

Tatu Lund edited this page Feb 7, 2026 · 1 revision

How to Test Vaadin 8 Components in Isolation with TestBench

This guide shows how to test reusable Vaadin 8 components and extensions in isolation using Vaadin TestBench, without going through the full application in vaadincreate-ui.

The examples are based on the vaadincreate-components module, which:

  • Packages generic components as a separate JAR.
  • Spins up a mini test application only in the test scope.
  • Uses a dedicated TestBench base class to drive a browser against that mini app.

The same pattern can be reused for any Vaadin 8 component library.


1. Why Test Components in Isolation?

This project has two main testing layers:

  • vaadincreate-components: tests generic building blocks (extensions, dialogs, helpers).
  • vaadincreate-ui: tests the actual application views that use those components.

By giving components their own mini application and TestBench tests, you:

  • Keep application ITs focused on view logic, not on low‑level component behavior.
  • Can reuse the same components in multiple projects but keep tests in a single place.
  • Run faster, more targeted tests when only component behavior changes.

The trade‑off is that the Vaadin widgetset is compiled twice during a full validation build:

  • Once for the component test app (vaadincreate-components).
  • Once for the real application (vaadincreate-ui).

Because the component test app lives in the test scope, it is not packaged into the production WAR/JAR.


2. Jetty Test App in vaadincreate-components pom.xml

The vaadincreate-components module is a plain JAR, but its pom.xml configures Jetty to run a small Vaadin app from the test classes:

<build>
  <defaultGoal>jetty:run</defaultGoal>
  <plugins>
    <plugin>
      <groupId>org.eclipse.jetty.ee8</groupId>
      <artifactId>jetty-ee8-maven-plugin</artifactId>
      <version>${jetty.plugin.version}</version>
      <configuration>
        <httpConnector>
          <port>8080</port>
        </httpConnector>
        <!-- Use test scope because the UI/demo classes are in the test package. -->
        <useTestScope>true</useTestScope>
        <supportedPackagings>
          <supportedPackaging>jar</supportedPackaging>
        </supportedPackagings>
        <webApp>
          <resourceBases>${basedir}/src/main/resources</resourceBases>
        </webApp>
      </configuration>
    </plugin>
    <!-- Vaadin widgetset compilation (runs for this module as well) -->
    <plugin>
      <groupId>com.vaadin</groupId>
      <artifactId>vaadin-maven-plugin</artifactId>
      <!-- ... compile widgetset and theme ... -->
    </plugin>
  </plugins>
</build>

Key points:

  • useTestScope>true tells Jetty to load UI classes from src/test/java, so the mini app exists only for tests.
  • Packaging stays jar – nothing from the test app leaks into the production artifact.
  • The Vaadin Maven plugin compiles a separate widgetset for the components.

Pro tips:

  • Keep the test app minimal: only include views and resources needed to exercise the components.
  • Reuse the same port (here 8080) as in other modules to keep TestBench base classes simple.

3. The Mini Components Application (ComponentsUI)

The mini application is a regular Vaadin 8 UI class under src/test/java:

@Theme(ValoTheme.THEME_NAME)
@Widgetset("org.vaadin.tatu.vaadincreate.components.WidgetSet")
public class ComponentsUI extends UI {

    Navigator nav;
    Map<String, Class<? extends View>> views = new HashMap<>();

    @Override
    protected void init(VaadinRequest request) {
        VerticalLayout content = new VerticalLayout();
        nav = new Navigator(this, content);

        addView("", DefaultView.class);
        addView(ResetButtonForTextFieldView.NAME, ResetButtonForTextFieldView.class);
        addView(ConfirmDialogView.NAME, ConfirmDialogView.class);
        // ... other component demo views ...

        setContent(content);
    }

    private void addView(String name, Class<? extends View> view) {
        nav.addView(name, view);
        views.put(name, view);
    }

    @WebServlet(value = "/*", asyncSupported = true)
    @VaadinServletConfiguration(productionMode = false, ui = ComponentsUI.class)
    public static class ComponentsServlet extends VaadinServlet { }
}

Each reusable component has its own simple view (for example ResetButtonForTextFieldView, ConfirmDialogView) which only wires that component and a bit of UI to interact with.

Pro tips:

  • Give each view a stable NAME constant and use #! fragments in tests (for example #!reset-button-for-text-field).
  • Keep views tiny and focused: one component per view, plus minimal labels/buttons to observe its behavior.

4. A Dedicated TestBench Base Class for Components

Component ITs extend a small TestBench base class that knows how to talk to the mini app on Jetty:

public abstract class AbstractComponentTest extends TestBenchTestCase {
    private static final int SERVER_PORT = 8080;
    private final String urlFragment;

    @Rule
    public ScreenshotOnFailureRule rule = new ScreenshotOnFailureRule(this, true);

    @BeforeClass
    public static void setupClass() {
        WebDriverManager.chromedriver().setup();
    }

    protected AbstractComponentTest(String route) {
        this.urlFragment = route;
    }

    @Before
    public void setup() throws Exception {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--headless=new");
        setDriver(TestBench.createDriver(new ChromeDriver(options)));
        getDriver().get(getURL(urlFragment));

        Parameters.setScreenshotComparisonTolerance(0.1);
        testBench().resizeViewPortTo(1280, 900);

        waitForAppLoaded();
    }

    public void waitForAppLoaded() {
        waitForElementPresent(By.id("org.vaadin.tatu.vaadincreate.components.WidgetSet"));
    }
}

Compared to the application‑level base class, this version:

  • Waits for the components widgetset to load.
  • Assumes the mini app on Jetty is already running.

Pro tips:

  • Keep one base class per test target (components vs. full app) so each can wait for the correct widgetset and root elements.
  • Put generic helpers (waiting, blur, viewport size, screenshot settings) here to keep component tests small.

5. Example: Testing a Reset Button Extension

The ResetButtonForTextField extension adds a small clickable clear button to a TextField. Its test view (ResetButtonForTextFieldView) is intentionally tiny:

public class ResetButtonForTextFieldView extends VerticalLayout implements View {
    public static final String NAME = "reset-button-for-text-field";

    public ResetButtonForTextFieldView() {
        TextField field = new TextField("Field");
        field.setValue("Value");
        ResetButtonForTextField.of(field);
        field.addValueChangeListener(e -> {
            Label label = new Label("Value:" + e.getValue());
            label.setId("value");
            addComponent(label);
        });
        addComponent(field);
    }
}

The TestBench IT focuses purely on the extension behavior:

public class ResetButtonForTextFieldIT extends AbstractComponentTest {

    @Override
    public void setup() throws Exception {
        super.setup();
        open("#!" + ResetButtonForTextFieldView.NAME);
    }

    @Test
    public void resetButtonFoundAndClearsWhenClicked() {
        TextFieldElement field = $(TextFieldElement.class).first();
        assertTrue(field.getClassNames()
                .contains("resetbuttonfortextfield-textfield"));

        WebElement button = driver.findElement(
                By.className("resetbuttonfortextfield-resetbutton"));
        assertTrue(button.isDisplayed());

        button.click();
        assertEquals("", field.getValue());

        LabelElement value = $(LabelElement.class).first();
        assertEquals("Value:", value.getText());
        assertFalse(button.isDisplayed());
    }
}

There is no dependency on the real application UI or navigation shell – only the component and its demo view.

Pro tips:

  • Assert both DOM details (CSS classes for the extension) and visible behavior (field value, labels, button visibility).
  • Use small helper views instead of trying to test a component inside a complex real view.

6. Example: Testing a Confirm Dialog Component

Another component, ConfirmDialog, is tested via a simple view with a single button:

public class ConfirmDialogView extends VerticalLayout implements View {
    public static final String NAME = "confirm-dialog";

    public ConfirmDialogView() {
        ConfirmDialog dialog = new ConfirmDialog(
                "Confirm", "Are you sure?", ConfirmDialog.Type.ALERT);
        dialog.addConfirmedListener(e -> Notification.show("Confirmed"));
        dialog.addCancelledListener(e -> Notification.show("Cancelled"));

        Button button = new Button("Open dialog", e -> dialog.open());
        addComponent(button);
    }
}

The integration test exercises the dialog with keyboard only:

public class ConfirmDialogIT extends AbstractComponentTest {

    @Override
    public void setup() throws Exception {
        super.setup();
        open("#!" + ConfirmDialogView.NAME);
    }

    @Test
    public void openAndCloseByEscapeAndEnter() {
        ButtonElement button = $(ButtonElement.class).first();
        button.click();

        new Actions(getDriver()).sendKeys(Keys.ESCAPE).perform();
        NotificationElement notification = $(NotificationElement.class).last();
        assertEquals("Cancelled", notification.getCaption());

        button.click();
        new Actions(getDriver()).sendKeys(Keys.ENTER).perform();
        notification = $(NotificationElement.class).last();
        assertEquals("Confirmed", notification.getCaption());
    }
}

Pro tips:

  • Most ConfirmDialog behavior (listeners, state changes, text) is covered in unit tests; this IT exists specifically to verify the JavaScript-based keyboard shortcuts (ESC / ENTER) in a real browser.

  • Focus on component contract: what should happen when users press ESC / ENTER, rather than on layout.

  • If the component has important ARIA or focus behavior, assert it here so the main application does not need to duplicate those checks.


7. How This Offloads the Main UI Tests

With this setup:

  • vaadincreate-components owns the behavioral tests for reusable components.
  • vaadincreate-ui tests can treat those components as trusted building blocks.

This means application‑level ITs can:

  • Focus on view composition, navigation, security and data wiring.
  • Call components like any other Vaadin component without repeating low‑level tests.

The cost is:

  • The Vaadin widgetset is compiled twice in a full build (once for the components module, once for the main app).
  • Jetty must be started for component tests, either via mvn test -pl vaadincreate-components or an integration‑tests profile.

For most medium‑sized projects, the clarity and reuse gained from isolated component tests outweighs the extra widgetset compilation time.


8. Summary and Adaptation Tips

To adapt this pattern to your own Vaadin 8 project:

  1. Create a components module

    • Package shared components/extensions into a separate Maven module.
    • Add Vaadin, TestBench, WebDriver and Jetty plugins similarly to vaadincreate-components.
  2. Build a tiny test‑only UI

    • Put a UI class and @WebServlet under src/test/java.
    • Register one simple View per component you want to test.
  3. Introduce a TestBench base class

    • Centralize WebDriver setup, viewport size and waiting logic.
    • Point it to the mini app on Jetty and wait for the correct widgetset.
  4. Write focused component ITs

    • One test class per component view.
    • Assert behavior, keyboard support and DOM details that define the component’s contract.
  5. Keep application ITs lean

    • In the main UI module, assume components behave correctly.
    • Only test how views compose and configure those components.

Following this approach, you get fast, isolated feedback on your reusable components while keeping your main application tests readable and maintainable, at the minor cost of an extra widgetset compilation in the build.

Clone this wiki locally