Skip to content

RPC for Vaadin TestBench

A library that lets Vaadin TestBench integration tests call server-side methods directly, with support for RMI-style remote object invocation and side-channel calls from a secondary tab.

Overview

The library exists to make Vaadin TestBench more practical for testing add-ons. The structure of an add-on's tests is different from the typical end-to-end test of an application:

  • Application tests follow the user's flow — actions are taken in the UI and the result is verified in the UI.
  • Add-on tests usually need to configure the component (by calling server-side setters), then trigger an action in the browser, and finally assert that the action changed server-side state.

On top of that, the views used for testing add-ons are not part of the add-on itself, so the test author has more leeway in how they're implemented. This library provides the plumbing needed to make those server-side calls from the test code expressed naturally.

Supported versions

Version 1.4+ of this library is compatible with Vaadin 14–25.

Commercial license required

This library is open source (Apache 2.0). However, to run integration tests you need a Commercial Vaadin Developer License version 4 for Vaadin TestBench.

Installation

Add the dependency to your pom.xml, with test scope:

<dependency>
    <groupId>com.flowingcode.vaadin.test</groupId>
    <artifactId>testbench-rpc</artifactId>
    <version>X.Y.Z</version>
    <scope>test</scope>
</dependency>

Releases are published to the Flowing Code releases repository:

<repository>
    <id>flowing-code-releases</id>
    <url>https://maven.flowingcode.com/releases</url>
</repository>

For snapshot builds, see maven.flowingcode.com/snapshots.

Getting started

1. Declare the client-callable interface

public interface SampleCallables {
    void showNotification(String message);
}

2. Implement it in the view, with @ClientCallable

@Route("")
public class SampleView extends Div implements SampleCallables {
    @Override
    @ClientCallable
    public void showNotification(String message) {
        Notification.show(message);
    }
}

3. Implement HasRpcSupport in the test and create a proxy

public class SampleIT extends AbstractViewTest implements HasRpcSupport {

    private static final String NOTIFICATION_MESSAGE = "RPC succeeded";

    public SampleIT() {
        super("");
    }

    SampleCallables $server = createCallableProxy(SampleCallables.class);

    @Test
    public void testNotification() {
        $server.showNotification(NOTIFICATION_MESSAGE);
        assertEquals(NOTIFICATION_MESSAGE, $(NotificationElement.class).first().getText());
    }
}

Argument and return types

Supported types on @ClientCallable methods:

Type Argument Return
boolean, int, double and their boxed forms
String
JsonValue
Enumeration types
Arrays of supported types

Returning lists

The return type of a @ClientCallable method can be declared as JsonArrayList<T> (where T is a supported return type, or Long). On the TestBench side, JsonArrayList implements Collection<T>, which makes it convenient to assert against with Hamcrest matchers:

@Override
@ClientCallable
public JsonArrayList<Integer> getIntegers() {
    return JsonArrayList.fromIntegers(Arrays.asList(1, 2));
}
@Test
public void testListInteger() {
    List<Integer> list = $server.getIntegers().asList();
    assertThat(list, Matchers.hasSize(2));
    assertThat(list.get(0), Matchers.equalTo(1));
    assertThat(list.get(1), Matchers.equalTo(2));
}

Remote method invocation (RMI)

The library provides an enhanced RMI-style mechanism that lets test code transparently invoke methods on a server-side object as if the object were local. The mechanism also lets the test environment manipulate the server-side instances — changing component state, modifying properties, or simulating scenarios to verify behavior.

Note

This mechanism follows RMI semantics but is not an implementation of Java RMI.

How it works

If the Vaadin view and the callable interface have support for RMI-style invocations, any interface extending RmiRemote — along with serializable classes and interfaces — can be used as a formal parameter or a return type. It is an error if a formal parameter or return type is a serializable class that implements RmiRemote.

Remote objects are automatically exported upon return or reference, so there is no need to "bind" them in a registry. Once a remote object is returned to the test code, it can be passed to further remote method calls; calling a method on a stub dispatches the invocation on the server side. During an invocation, stubs representing remote objects are transmitted in place of the actual objects:

  • In the test code, stubs for the same remote object compare equal but are not necessarily the same instance.
  • In the application code, stubs resolve to the same original remote instance.

When non-remote objects are passed or returned, they are copied through Java serialization — changes to the copy do not affect the original. Referential integrity is still guaranteed within a single remote-method call (multiple references to the same object in arguments of one call still refer to the same instance). When passing or returning values by copy, it is a runtime error if the actual value is a Component or references a Component.

Remote objects share the lifecycle of the view and are garbage collected when the view is collected.

Version compatibility

The application and the integration tests must use the same classes and the same version of RPC for Vaadin TestBench — there is no protocol negotiation or dynamic classloading.

Getting started with RMI

1. Make the callable interface extend RmiCallable

public interface SampleCallables extends RmiCallable {
    // ...
}

2. Override $call in the view and annotate it with @ClientCallable

This is a workaround for vaadin/flow#17098:

@Route("")
public class SampleView extends Div implements SampleCallables {
    @Override
    @ClientCallable
    public JsonValue $call(JsonObject invocation) {
        return SampleCallables.super.$call(invocation);
    }
}

3. Use interfaces extending RmiRemote as argument or return types

interface MyRemoteObject extends RmiRemote {
    String getName();
}

public interface SampleCallables extends RmiCallable {
    MyRemoteObject createRemote(String name);
    void receiveRemote(MyRemoteObject remote);
}

4. Implement the additional methods in the view (without @ClientCallable)

Remote objects are automatically registered with RMI:

@Override
public MyRemoteObject createRemote(String name) {
    return new MyRemoteObjectImpl(name);
}

@Override
public void receiveRemote(MyRemoteObject remote) {
    // ...
}

5. In the test, methods called on the remote stubs execute on the server

@Test
public void testRemoteObject() {
    MyRemoteObject remote = $server.createRemote("foo");
    $server.receiveRemote(remote);
    assertEquals("foo", remote.getName());
}

Side-channel invocation

Side-channel invocation lets the test invoke methods (RMI or client-callable RPC) from another view, useful when exposing methods from reusable views.

Setup

1. Initialize the proxy with a URL

The URL is opened in a second tab; calls dispatch from that tab. TestBench RPC switches back to the original tab when the call completes:

OtherCallables $server = createCallableProxy(OtherCallables.class, getURL("other"));

2. Optionally implement SideChannelSupport in the callable interface

This adds a closeSideChannel() method to the proxy:

public interface OtherCallables extends SideChannelSupport { ... }

RMI stubs are UI-scoped

When using RMI, remember that stubs are scoped to a UI. Closing the side channel invalidates all of its stubs.

Building and running the demo

git clone https://github.com/FlowingCode/testbench-rpc.git
cd testbench-rpc
mvn -Pit -Pdemo -Pproduction verify

The Vaadin application starts and a sample integration test runs (the application opens at http://localhost:8080/ but there is nothing to see there).

Source code

testbench-rpc on GitHub — distributed under Apache License 2.0.