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.