diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 453d9297e35aaedfbc03b908516840b535e6a061..57918916854c5164785fb587774953250a772bff 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,6 +4,8 @@ variables:
 stages:
 - build
 # - publish
+# - test
+# - publish
 
 before_script:
   - export GRADLE_USER_HOME=`pwd`/.gradle
@@ -20,7 +22,21 @@ build:
     - ./gradlew --console=plain --no-daemon assemble jar
   artifacts:
     paths:
-      - "/builds/jastadd/ragconnect/build/libs/ragconnect-*.jar"
+      - "/builds/jastadd/ragconnect/ragconnect.base/build/libs/ragconnect-*.jar"
+    expire_in: 1 week
+
+test:
+  image: openjdk:11
+  stage: test
+  services:
+    - name: "eclipse-mosquitto:1.6.9"
+      alias: "mqtt"
+  script:
+    - ./gradlew --console=plain --no-daemon allTests
+  artifacts:
+    reports:
+      junit: "/builds/jastadd/ragconnect/ragconnect.tests/build/test-results/test/TEST-*.xml"
+    expire_in: 1 week
 
 #publish:
 #  image: openjdk:11
diff --git a/libs/buildSrc.jar b/libs/buildSrc.jar
new file mode 100644
index 0000000000000000000000000000000000000000..b0ca2cbc4fc7f12022592944d7446a429d855add
Binary files /dev/null and b/libs/buildSrc.jar differ
diff --git a/ragconnect.base/build.gradle b/ragconnect.base/build.gradle
index 5e95789763190f9f2dac3abe7c9b59c0de28ff59..b3e89a6a7fe602f4bfa87611e3002e99b446f7c6 100644
--- a/ragconnect.base/build.gradle
+++ b/ragconnect.base/build.gradle
@@ -46,6 +46,12 @@ try {
     throw new GradleException("File ${versionFile} not found or unreadable. Aborting.", e)
 }
 
+task printVersion() {
+    doLast {
+        println(version)
+    }
+}
+
 task newVersion() {
     doFirst {
         def props = new Properties()
@@ -61,6 +67,9 @@ idea.module.generatedSourceDirs += genSrc
 jar {
     manifest {
         attributes "Main-Class": 'org.jastadd.ragconnect.compiler.Compiler'
+
+        // Log4J + Java 11 compatibility, see https://stackoverflow.com/q/53049346/2493208
+        attributes "Multi-Release": true
     }
 
     from {
diff --git a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
index 06d223ee1f4a3fd0cc78beeaa2a8cedf0a2bbd45..0c9190a1cfeed156c6353495f327244bef3ed4b0 100644
--- a/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
+++ b/ragconnect.base/src/main/jastadd/intermediate/Generation.jadd
@@ -29,6 +29,17 @@ aspect AttributesForMustache {
 
   syn String MEndpointDefinition.connectParameterName() = "uriString";
   syn String MEndpointDefinition.connectMethod() = "connect" + tokenName();
+
+  syn String MEndpointDefinition.disconnectMethod() {
+    // if both (send and receive) are defined for the token, ensure methods with different names
+    String extra = endpointDef().lookupTokenEndpointDefinitions(token()).size() > 1 ? uniqueSuffix() : "";
+    return "disconnect" + extra + tokenName();
+  }
+  //
+  syn String MEndpointDefinition.uniqueSuffix();
+  eq MSendDefinition.uniqueSuffix() = "Send";
+  eq MReceiveDefinition.uniqueSuffix() = "Receive";
+
   syn TokenComponent MEndpointDefinition.token() = endpointDef().getToken();
   syn boolean MEndpointDefinition.alwaysApply() = endpointDef().getAlwaysApply();
   syn String MEndpointDefinition.parentTypeName() = token().containingTypeDecl().getName();
diff --git a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
index 0c4900df464e839ab1ea3108cc82eecfc697549e..8c18f32bef6489aaea520250d0101275f314a41c 100644
--- a/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
+++ b/ragconnect.base/src/main/java/org/jastadd/ragconnect/compiler/Compiler.java
@@ -142,7 +142,9 @@ public class Compiler extends AbstractCompiler {
   }
 
   private void printMessage(String message) {
-    System.out.println(message);
+    if (optionVerbose.value()) {
+      System.out.println(message);
+    }
   }
 
   private void writeToFile(Path path, String str) throws CompilerException {
diff --git a/ragconnect.base/src/main/resources/MqttHandler.jadd b/ragconnect.base/src/main/resources/MqttHandler.jadd
index b6750261d9f4ad06d92721c95aa7aa6e7971849b..f5e6ba99b3058f59f6b9b40ae0d9c07f18da88c3 100644
--- a/ragconnect.base/src/main/resources/MqttHandler.jadd
+++ b/ragconnect.base/src/main/resources/MqttHandler.jadd
@@ -4,6 +4,7 @@ import java.util.concurrent.TimeUnit;
 aspect MqttHandler {
 public class MqttServerHandler {
   private final java.util.Map<String, MqttHandler> handlers = new java.util.HashMap<>();
+  private final java.util.Map<ConnectToken, Object> tokensForRemoval = new java.util.HashMap<>();
   private long time;
   private java.util.concurrent.TimeUnit unit;
   private String name;
@@ -38,8 +39,16 @@ public class MqttServerHandler {
     return handler;
   }
 
-  public boolean newConnection(java.net.URI uri, java.util.function.Consumer<byte[]> callback) throws IOException {
-    return resolveHandler(uri).newConnection(extractTopic(uri), callback);
+  public ConnectToken newConnection(java.net.URI uri, java.util.function.Consumer<byte[]> callback) throws IOException {
+    ConnectToken connectToken = new ConnectToken(uri);
+    resolveHandler(uri).newConnection(extractTopic(uri), callback);
+    tokensForRemoval.put(connectToken, callback);
+    return connectToken;
+  }
+
+  public boolean disconnect(ConnectToken connectToken) throws IOException {
+    MqttHandler handler = resolveHandler(connectToken.uri);
+    return handler != null ? handler.disconnect(extractTopic(connectToken.uri), tokensForRemoval.get(connectToken)) : false;
   }
 
   public void publish(java.net.URI uri, byte[] bytes) throws IOException {
@@ -146,14 +155,16 @@ public class MqttHandler {
       }
 
       @Override
-      public void onPublish(org.fusesource.hawtbuf.UTF8Buffer topic, org.fusesource.hawtbuf.Buffer body, org.fusesource.mqtt.client.Callback<org.fusesource.mqtt.client.Callback<Void>> ack) {
+      public void onPublish(org.fusesource.hawtbuf.UTF8Buffer topic,
+                            org.fusesource.hawtbuf.Buffer body,
+                            org.fusesource.mqtt.client.Callback<org.fusesource.mqtt.client.Callback<Void>> ack) {
+        // this method is called, whenever a MQTT message is received
         String topicString = topic.toString();
         java.util.List<java.util.function.Consumer<byte[]>> callbackList = callbacks.get(topicString);
         if (callbackList == null || callbackList.isEmpty()) {
           logger.debug("Got a message, but no callback to call. Forgot to subscribe?");
         } else {
           byte[] message = body.toByteArray();
-//          System.out.println("message = " + Arrays.toString(message));
           for (java.util.function.Consumer<byte[]> callback : callbackList) {
             callback.accept(message);
           }
@@ -162,13 +173,15 @@ public class MqttHandler {
       }
 
       @Override
-      public void onPublish(org.fusesource.hawtbuf.UTF8Buffer topicBuffer, org.fusesource.hawtbuf.Buffer body, Runnable ack) {
+      public void onPublish(org.fusesource.hawtbuf.UTF8Buffer topicBuffer,
+                            org.fusesource.hawtbuf.Buffer body,
+                            Runnable ack) {
+        // not used by this type of connection
         logger.warn("onPublish should not be called");
       }
 
       @Override
       public void onFailure(Throwable cause) {
-//        logger.catching(cause);
         error.set(cause);
       }
     });
@@ -179,7 +192,11 @@ public class MqttHandler {
       @Override
       public void onSuccess(Void value) {
         if (MqttHandler.this.sendWelcomeMessage) {
-          connection.publish("components", (name + " is connected").getBytes(), org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE, false, new org.fusesource.mqtt.client.Callback<Void>() {
+          connection.publish("components",
+                             (name + " is connected").getBytes(),
+                             org.fusesource.mqtt.client.QoS.AT_LEAST_ONCE,
+                             false,
+                             new org.fusesource.mqtt.client.Callback<Void>() {
             @Override
             public void onSuccess(Void value) {
               logger.debug("success sending welcome message");
@@ -198,7 +215,6 @@ public class MqttHandler {
 
       @Override
       public void onFailure(Throwable cause) {
-//        logger.error("Could not connect", cause);
         error.set(cause);
       }
     });
@@ -228,8 +244,6 @@ public class MqttHandler {
     if (readyLatch.getCount() > 0) {
       System.err.println("Handler not ready");
       return false;
-//      // should maybe be something more kind than throwing an exception here
-//      throw new IllegalStateException("Updater not ready");
     }
     // register callback
     logger.debug("new connection for {}", topic);
@@ -256,6 +270,35 @@ public class MqttHandler {
     return true;
   }
 
+  public boolean disconnect(String topic, Object callback) {
+    java.util.List<java.util.function.Consumer<byte[]>> callbackList = callbacks.get(topic);
+    if (callbackList == null) {
+      logger.warn("Disconnect for not connected topic '{}'", topic);
+      return false;
+    }
+    java.util.concurrent.atomic.AtomicReference<Boolean> success = new java.util.concurrent.atomic.AtomicReference<>();
+    success.set(callbackList.remove(callback));
+    if (callbackList.isEmpty()) {
+      // no callbacks anymore for this topic, unsubscribe from mqtt
+      connection.getDispatchQueue().execute(() -> {
+        org.fusesource.hawtbuf.UTF8Buffer topicBuffer = org.fusesource.hawtbuf.Buffer.utf8(topic);
+        org.fusesource.hawtbuf.UTF8Buffer[] topicArray = new org.fusesource.hawtbuf.UTF8Buffer[]{topicBuffer};
+        connection.unsubscribe(topicArray, new org.fusesource.mqtt.client.Callback<Void>() {
+          @Override
+          public void onSuccess(Void value) {
+            // empty, all good
+          }
+
+          @Override
+          public void onFailure(Throwable cause) {
+            success.set(false);
+          }
+        });
+      });
+    }
+    return success.get();
+  }
+
   /**
    * Waits until this updater is ready to receive MQTT messages.
    * If it already is ready, return immediately with the value <code>true</code>.
diff --git a/ragconnect.base/src/main/resources/RestHandler.jadd b/ragconnect.base/src/main/resources/RestHandler.jadd
index f7b1a83304fe7aaebe97d105514c3f192d052aec..b69bf71b73ba9d4c5a9f1998ee2dd92ede77561d 100644
--- a/ragconnect.base/src/main/resources/RestHandler.jadd
+++ b/ragconnect.base/src/main/resources/RestHandler.jadd
@@ -2,6 +2,7 @@ import java.util.concurrent.TimeUnit;aspect RestHandler {
 public class RestServerHandler {
   private static final int DEFAULT_PORT = 4567;
   private final java.util.Map<Integer, RestHandler> handlers = new java.util.HashMap<>();
+  private final java.util.Map<ConnectToken, Object> tokensForRemoval = new java.util.HashMap<>();
   private String name;
 
   public RestServerHandler() {
@@ -24,14 +25,23 @@ public class RestServerHandler {
     return handler;
   }
 
-  public boolean newPUTConnection(java.net.URI uri, java.util.function.Consumer<String> callback) {
+  public ConnectToken newPUTConnection(java.net.URI uri, java.util.function.Consumer<String> callback) {
+    ConnectToken connectToken = new ConnectToken(uri);
     resolveHandler(uri).newPUTConnection(uri.getPath(), callback);
-    return true;
+    tokensForRemoval.put(connectToken, callback);
+    return connectToken;
   }
 
-  public boolean newGETConnection(java.net.URI uri, SupplierWithException<String> supplier) {
+  public ConnectToken newGETConnection(java.net.URI uri, SupplierWithException<String> supplier) {
+    ConnectToken connectToken = new ConnectToken(uri);
     resolveHandler(uri).newGETConnection(uri.getPath(), supplier);
-    return true;
+    tokensForRemoval.put(connectToken, supplier);
+    return connectToken;
+  }
+
+  public boolean disconnect(ConnectToken connectToken) {
+    RestHandler handler = resolveHandler(connectToken.uri);
+    return handler != null ? handler.disconnect(connectToken.uri.getPath(), tokensForRemoval.get(connectToken)) : false;
   }
 
   public void close() {
@@ -108,6 +118,7 @@ public class RestHandler {
     suppliers.put(path, supplier);
     spark.Spark.get(path, (request, response) -> {
       try {
+        // we could check for null here in case supplier has been disconnected
         return supplier.get();
       } catch (Exception e) {
         return makeError(response, 500, e.getMessage());
@@ -115,6 +126,12 @@ public class RestHandler {
     });
   }
 
+  public boolean disconnect(String path, Object callbackOrSupplier) {
+    // only one will succeed (or false will be returned)
+    return callbacks.getOrDefault(path, java.util.Collections.emptyList()).remove(callbackOrSupplier) ||
+           suppliers.remove(path, callbackOrSupplier);
+  }
+
   private String makeError(spark.Response response, int statusCode, String message) {
     response.status(statusCode);
     return message;
diff --git a/ragconnect.base/src/main/resources/handler.mustache b/ragconnect.base/src/main/resources/handler.mustache
index 796f76027fd22899b60e02a2fdd08fbf0dc9ebdb..41e42387f69239f054b8ddaafa80680b13ca7591 100644
--- a/ragconnect.base/src/main/resources/handler.mustache
+++ b/ragconnect.base/src/main/resources/handler.mustache
@@ -14,4 +14,15 @@ aspect RagConnectHandler {
     {{#InUse}}{{FieldName}}.close();{{/InUse}}
     {{/Handlers}}
   }
+  class ConnectToken {
+    static java.util.concurrent.atomic.AtomicLong counter = new java.util.concurrent.atomic.AtomicLong(0);
+    final long id;
+    final java.net.URI uri;
+    public ConnectToken(java.net.URI uri) {
+      this.id = counter.incrementAndGet();
+      this.uri = uri;
+    }
+
+  }
+  static java.util.Map<ASTNode, java.util.Map<java.net.URI, ConnectToken>> ASTNode.connectTokens = new java.util.HashMap<>();
 }
diff --git a/ragconnect.base/src/main/resources/ragConnectVersion.properties b/ragconnect.base/src/main/resources/ragConnectVersion.properties
index 02738667ad14ade7495694af0fe23995c160b019..266543a066a77220bc13c577f38b08368a2f4d40 100644
--- a/ragconnect.base/src/main/resources/ragConnectVersion.properties
+++ b/ragconnect.base/src/main/resources/ragConnectVersion.properties
@@ -1,2 +1,2 @@
-#Fri Jan 15 11:45:51 CET 2021
-version=0.2.4
+#Tue Jan 19 12:08:02 CET 2021
+version=0.2.5
diff --git a/ragconnect.base/src/main/resources/receiveDefinition.mustache b/ragconnect.base/src/main/resources/receiveDefinition.mustache
index 95340e5446baeab28564747076c4178b06d55ee6..5294f897593efb8928e8114dd488777e82b90357 100644
--- a/ragconnect.base/src/main/resources/receiveDefinition.mustache
+++ b/ragconnect.base/src/main/resources/receiveDefinition.mustache
@@ -7,17 +7,46 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam
     {{/loggingEnabledForReads}}
     set{{tokenName}}({{lastResult}});
   };
+  ConnectToken connectToken;
   switch (scheme) {
   {{#usesMqtt}}
-    case "mqtt": return {{mqttHandlerAttribute}}().newConnection(uri, consumer);
+    case "mqtt":
+      connectToken = {{mqttHandlerAttribute}}().newConnection(uri, consumer);
+      if (connectToken == null) {
+        return false;
+      }
+      break;
   {{/usesMqtt}}
   {{#usesJava}}
     case "java": return {{javaHandlerAttribute}}().registerCallback(path, consumer);
   {{/usesJava}}
   {{#usesRest}}
-    case "rest": return {{restHandlerAttribute}}().newPUTConnection(uri, input -> {
-      consumer.accept(input.getBytes());
-    });
+    case "rest":
+      connectToken = {{restHandlerAttribute}}().newPUTConnection(uri, input -> {
+        consumer.accept(input.getBytes());
+      });
+      if (connectToken == null) {
+        return false;
+      }
+      break;
+  {{/usesRest}}
+    default:
+      System.err.println("Unknown protocol '" + scheme + "'.");
+      return false;
+  }
+  connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>())
+               .put(uri, connectToken);
+  return true;
+}
+
+public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameterName}}) throws java.io.IOException {
+  {{>handleUri}}
+  switch (scheme) {
+  {{#usesMqtt}}
+    case "mqtt": return {{mqttHandlerAttribute}}().disconnect(connectTokens.get(this).get(uri));
+  {{/usesMqtt}}
+  {{#usesRest}}
+    case "rest": return {{restHandlerAttribute}}().disconnect(connectTokens.get(this).get(uri));
   {{/usesRest}}
     default:
       System.err.println("Unknown protocol '" + scheme + "'.");
diff --git a/ragconnect.base/src/main/resources/sendDefinition.mustache b/ragconnect.base/src/main/resources/sendDefinition.mustache
index 248ea2db939f1da946e62ce7ce2034457adaf4cb..e564ef85453221fdcd6e5519a03f28bf43cf2927 100644
--- a/ragconnect.base/src/main/resources/sendDefinition.mustache
+++ b/ragconnect.base/src/main/resources/sendDefinition.mustache
@@ -35,10 +35,36 @@ public boolean {{parentTypeName}}.{{connectMethod}}(String {{connectParameterNam
   {{/usesJava}}
   {{#usesRest}}
     case "rest":
-      {{restHandlerAttribute}}().newGETConnection(uri, () -> {
+      ConnectToken connectToken = {{restHandlerAttribute}}().newGETConnection(uri, () -> {
         {{updateMethod}}();
         return new String({{lastValue}});
       });
+      if (connectToken == null) {
+        return false;
+      }
+      connectTokens.computeIfAbsent(this, astNode -> new java.util.HashMap<java.net.URI, ConnectToken>())
+                   .put(uri, connectToken);
+      break;
+  {{/usesRest}}
+    default:
+      System.err.println("Unknown protocol '" + scheme + "'.");
+      return false;
+  }
+  return true;
+}
+
+public boolean {{parentTypeName}}.{{disconnectMethod}}(String {{connectParameterName}}) throws java.io.IOException {
+  {{>handleUri}}
+  switch (scheme) {
+  {{#usesMqtt}}
+    case "mqtt":
+      {{sender}} = null;
+      {{lastValue}} = null;
+      break;
+  {{/usesMqtt}}
+  {{#usesRest}}
+    case "rest":
+      {{restHandlerAttribute}}().disconnect(connectTokens.get(this).get(uri));
       break;
   {{/usesRest}}
     default:
diff --git a/ragconnect.tests/.gitignore b/ragconnect.tests/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..87b4cdd3d7c6a41502ca98703abeeb69a1d536fb
--- /dev/null
+++ b/ragconnect.tests/.gitignore
@@ -0,0 +1,5 @@
+build
+src/gen-res/
+src/gen/
+out/
+*.class
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..ccc0134eb86898cd001183828f81652f2a8c2010
--- /dev/null
+++ b/ragconnect.tests/build.gradle
@@ -0,0 +1,340 @@
+buildscript {
+    repositories.mavenCentral()
+    dependencies {
+        classpath 'org.jastadd:jastaddgradle:1.13.3'
+        classpath fileTree(include: ['buildSrc.jar'], dir: '../libs')
+    }
+}
+
+import org.jastadd.relast.plugin.RelastPlugin
+import org.jastadd.relast.plugin.RelastTest
+
+plugins {
+    id 'java'
+    id 'java-library'
+    id 'idea'
+    id 'com.github.ben-manes.versions' version '0.36.0'
+    id 'com.google.protobuf' version "0.8.14"
+}
+
+apply plugin: 'jastadd'
+apply plugin: RelastPlugin
+
+group = 'de.tudresden.inf.st'
+
+repositories {
+    mavenCentral()
+    jcenter()
+}
+
+dependencies {
+    implementation project(':ragconnect.base')
+
+    runtime group: 'org.jastadd', name: 'jastadd', version: '2.3.4'
+    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.0'
+    testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.4.0'
+    testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.12.1'
+
+    // mqtt
+    testImplementation group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'
+
+    // rest and client
+    testImplementation group: 'com.sparkjava', name: 'spark-core', version: '2.9.2'
+    testImplementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.2'
+    testImplementation group: 'org.glassfish.jersey.core', name: 'jersey-client', version: '2.31'
+    testImplementation group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '2.31'
+
+    testImplementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
+    api group: 'com.google.protobuf', name: 'protobuf-java', version: '3.0.0'
+}
+
+test {
+    useJUnitPlatform {
+        excludeTags 'mqtt'
+    }
+
+    maxHeapSize = '1G'
+}
+
+protobuf {
+    protoc {
+        // The artifact spec for the Protobuf Compiler
+        artifact = 'com.google.protobuf:protoc:3.0.0'
+    }
+}
+
+task allTests(type: Test, dependsOn: testClasses) {
+    description = 'Run every test'
+    group = 'verification'
+
+    useJUnitPlatform {
+        includeTags 'mqtt'
+    }
+}
+
+relastTest {
+    //noinspection GroovyAssignabilityCheck
+    compilerLocation = '../libs/relast.jar'
+}
+
+File genSrc = file("src/test/java-gen")
+sourceSets.test.java.srcDir genSrc
+idea.module.generatedSourceDirs += genSrc
+
+clean {
+    delete 'src/test/02-after-ragconnect/*/', 'src/test/03-after-relast/*/', 'src/test/java-gen/*/'
+}
+
+// --- Test: Example ---
+task preprocessExampleTest(type: JavaExec, group: 'verification') {
+    doFirst {
+        delete 'src/test/02-after-ragconnect/example/Test.relast',
+                'src/test/02-after-ragconnect/example/MqttHandler.jadd',
+                'src/test/02-after-ragconnect/example/RagConnect.jadd'
+    }
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'org.jastadd.ragconnect.compiler.Compiler'
+    args '--o=src/test/02-after-ragconnect/example',
+            'src/test/01-input/example/Test.relast',
+            'src/test/01-input/example/Test.connect',
+            '--rootNode=Model',
+            '--logReads', '--logWrites', '--protocols=mqtt'
+}
+
+task compileExampleTest(type: RelastTest) {
+    useJastAddNames = true
+    jastAddList = 'JastAddList'
+    relastFiles 'src/test/02-after-ragconnect/example/Test.relast',
+            'src/test/02-after-ragconnect/example/RagConnect.relast'
+    grammarName = 'src/test/03-after-relast/example/example'
+    packageName = 'example.ast'
+    moreInputFiles 'src/test/01-input/example/Test.jadd',
+            'src/test/02-after-ragconnect/example/MqttHandler.jadd',
+            'src/test/02-after-ragconnect/example/RagConnect.jadd'
+}
+
+compileTestJava.dependsOn compileExampleTest
+compileExampleTest.dependsOn preprocessExampleTest
+
+// --- Test: default-only-read ---
+task preprocessDefaultOnlyReadTest(type: JavaExec, group: 'verification') {
+    doFirst {
+        delete 'src/test/02-after-ragconnect/defaultOnlyRead/Test.relast',
+                'src/test/02-after-ragconnect/defaultOnlyRead/MqttHandler.jadd',
+                'src/test/02-after-ragconnect/defaultOnlyRead/RagConnect.jadd'
+    }
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'org.jastadd.ragconnect.compiler.Compiler'
+    args '--o=src/test/02-after-ragconnect/defaultOnlyRead',
+            'src/test/01-input/defaultOnlyRead/Test.relast',
+            'src/test/01-input/defaultOnlyRead/Test.connect',
+            '--rootNode=A', '--protocols=mqtt'
+}
+
+task compileDefaultOnlyReadTest(type: RelastTest) {
+    useJastAddNames = true
+    jastAddList = 'JastAddList'
+    relastFiles 'src/test/02-after-ragconnect/defaultOnlyRead/Test.relast',
+            'src/test/02-after-ragconnect/defaultOnlyRead/RagConnect.relast'
+    grammarName = 'src/test/03-after-relast/defaultOnlyRead/defaultOnlyRead'
+    packageName = 'defaultOnlyRead.ast'
+    moreInputFiles 'src/test/02-after-ragconnect/defaultOnlyRead/MqttHandler.jadd',
+            'src/test/02-after-ragconnect/defaultOnlyRead/RagConnect.jadd'
+}
+
+compileTestJava.dependsOn compileDefaultOnlyReadTest
+compileDefaultOnlyReadTest.dependsOn preprocessDefaultOnlyReadTest
+
+// --- Test: default-only-write ---
+task preprocessDefaultOnlyWriteTest(type: JavaExec, group: 'verification') {
+    doFirst {
+        delete 'src/test/02-after-ragconnect/defaultOnlyWrite/Test.relast',
+                'src/test/02-after-ragconnect/defaultOnlyWrite/MqttHandler.jadd',
+                'src/test/02-after-ragconnect/defaultOnlyWrite/RagConnect.jadd'
+    }
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'org.jastadd.ragconnect.compiler.Compiler'
+    args '--o=src/test/02-after-ragconnect/defaultOnlyWrite',
+            'src/test/01-input/defaultOnlyWrite/Test.relast',
+            'src/test/01-input/defaultOnlyWrite/Test.connect',
+            '--rootNode=A', '--protocols=mqtt'
+}
+
+task compileDefaultOnlyWriteTest(type: RelastTest) {
+    useJastAddNames = true
+    jastAddList = 'JastAddList'
+    relastFiles 'src/test/02-after-ragconnect/defaultOnlyWrite/Test.relast',
+            'src/test/02-after-ragconnect/defaultOnlyWrite/RagConnect.relast'
+    grammarName = 'src/test/03-after-relast/defaultOnlyWrite/defaultOnlyWrite'
+    packageName = 'defaultOnlyWrite.ast'
+    moreInputFiles 'src/test/01-input/defaultOnlyWrite/Test.jadd',
+            'src/test/02-after-ragconnect/defaultOnlyWrite/MqttHandler.jadd',
+            'src/test/02-after-ragconnect/defaultOnlyWrite/RagConnect.jadd'
+}
+
+compileTestJava.dependsOn compileDefaultOnlyWriteTest
+compileDefaultOnlyWriteTest.dependsOn preprocessDefaultOnlyWriteTest
+
+// --- Test: read1write2 ---
+task preprocessRead1Write2Test(type: JavaExec, group: 'verification') {
+    doFirst {
+        delete 'src/test/02-after-ragconnect/read1write2/Test.relast',
+                'src/test/02-after-ragconnect/read1write2/MqttHandler.jadd',
+                'src/test/02-after-ragconnect/read1write2/RagConnect.jadd'
+    }
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'org.jastadd.ragconnect.compiler.Compiler'
+    args '--o=src/test/02-after-ragconnect/read1write2',
+            'src/test/01-input/read1write2/Test.relast',
+            'src/test/01-input/read1write2/Test.connect',
+            '--rootNode=A', '--protocols=mqtt'
+}
+
+task compileRead1Write2Test(type: RelastTest) {
+    useJastAddNames = true
+    jastAddList = 'JastAddList'
+    relastFiles 'src/test/02-after-ragconnect/read1write2/Test.relast',
+            'src/test/02-after-ragconnect/read1write2/RagConnect.relast'
+    grammarName = 'src/test/03-after-relast/read1write2/read1write2'
+    packageName = 'read1write2.ast'
+    moreInputFiles 'src/test/01-input/read1write2/Test.jadd',
+            'src/test/02-after-ragconnect/read1write2/MqttHandler.jadd',
+            'src/test/02-after-ragconnect/read1write2/RagConnect.jadd'
+}
+
+compileTestJava.dependsOn compileRead1Write2Test
+compileRead1Write2Test.dependsOn preprocessRead1Write2Test
+
+// --- Test: read2write1 ---
+task preprocessRead2Write1Test(type: JavaExec, group: 'verification') {
+    doFirst {
+        delete 'src/test/02-after-ragconnect/read2write1/Test.relast',
+                'src/test/02-after-ragconnect/read2write1/MqttHandler.jadd',
+                'src/test/02-after-ragconnect/read2write1/RagConnect.jadd'
+    }
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'org.jastadd.ragconnect.compiler.Compiler'
+    args '--o=src/test/02-after-ragconnect/read2write1',
+            'src/test/01-input/read2write1/Test.relast',
+            'src/test/01-input/read2write1/Test.connect',
+            '--rootNode=A', '--protocols=mqtt'
+}
+
+task compileRead2Write1Test(type: RelastTest) {
+    useJastAddNames = true
+    jastAddList = 'JastAddList'
+    relastFiles 'src/test/02-after-ragconnect/read2write1/Test.relast',
+            'src/test/02-after-ragconnect/read2write1/RagConnect.relast'
+    grammarName = 'src/test/03-after-relast/read2write1/read2write1'
+    packageName = 'read2write1.ast'
+    moreInputFiles 'src/test/01-input/read2write1/Test.jadd',
+            'src/test/02-after-ragconnect/read2write1/MqttHandler.jadd',
+            'src/test/02-after-ragconnect/read2write1/RagConnect.jadd'
+}
+
+compileTestJava.dependsOn compileRead2Write1Test
+compileRead2Write1Test.dependsOn preprocessRead2Write1Test
+
+// --- Test: via ---
+task preprocessViaTest(type: JavaExec, group: 'verification') {
+    doFirst {
+        delete 'src/test/02-after-ragconnect/via/Test.relast',
+                'src/test/02-after-ragconnect/via/MqttHandler.jadd',
+                'src/test/02-after-ragconnect/via/RestHandler.jadd',
+                'src/test/02-after-ragconnect/via/RagConnect.jadd'
+    }
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'org.jastadd.ragconnect.compiler.Compiler'
+    args '--o=src/test/02-after-ragconnect/via',
+            'src/test/01-input/via/Test.relast',
+            'src/test/01-input/via/Test.connect',
+            '--rootNode=A',
+            '--protocols=mqtt,rest'
+}
+
+task compileViaTest(type: RelastTest) {
+    useJastAddNames = true
+    jastAddList = 'JastAddList'
+    relastFiles 'src/test/02-after-ragconnect/via/Test.relast',
+            'src/test/02-after-ragconnect/via/RagConnect.relast'
+    grammarName = 'src/test/03-after-relast/via/via'
+    packageName = 'via.ast'
+    moreInputFiles 'src/test/01-input/via/Test.jadd',
+            'src/test/02-after-ragconnect/via/MqttHandler.jadd',
+            'src/test/02-after-ragconnect/via/RestHandler.jadd',
+            'src/test/02-after-ragconnect/via/RagConnect.jadd'
+}
+
+compileTestJava.dependsOn compileViaTest
+compileViaTest.dependsOn preprocessViaTest
+
+// --- Test: token-value-send ---
+task preprocessTokenValueSendTest(type: JavaExec, group: 'verification') {
+    doFirst {
+        delete 'src/test/02-after-ragconnect/tokenValueSend/Test.relast',
+                'src/test/02-after-ragconnect/tokenValueSend/MqttHandler.jadd',
+                'src/test/02-after-ragconnect/tokenValueSend/RestHandler.jadd',
+                'src/test/02-after-ragconnect/tokenValueSend/RagConnect.jadd'
+    }
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'org.jastadd.ragconnect.compiler.Compiler'
+    args '--o=src/test/02-after-ragconnect/tokenValueSend',
+            'src/test/01-input/tokenValueSend/Test.relast',
+            'src/test/01-input/tokenValueSend/Test.connect',
+            '--rootNode=A',
+            '--protocols=mqtt'
+}
+
+task compileTokenValueSendTest(type: RelastTest) {
+    useJastAddNames = true
+    jastAddList = 'JastAddList'
+    relastFiles 'src/test/02-after-ragconnect/tokenValueSend/Test.relast',
+            'src/test/02-after-ragconnect/tokenValueSend/RagConnect.relast'
+    grammarName = 'src/test/03-after-relast/tokenValueSend/tokenValueSend'
+    packageName = 'tokenValueSend.ast'
+    moreInputFiles 'src/test/01-input/tokenValueSend/Test.jadd',
+            'src/test/02-after-ragconnect/tokenValueSend/MqttHandler.jadd',
+            'src/test/02-after-ragconnect/tokenValueSend/RagConnect.jadd'
+}
+
+compileTestJava.dependsOn compileTokenValueSendTest
+compileTokenValueSendTest.dependsOn preprocessTokenValueSendTest
+
+// --- Test: tutorial ---
+task preprocessTutorialTest(type: JavaExec, group: 'verification') {
+    doFirst {
+        delete 'src/test/02-after-ragconnect/tutorial/Test.relast',
+                'src/test/02-after-ragconnect/tutorial/MqttHandler.jadd',
+                'src/test/02-after-ragconnect/tutorial/RagConnect.jadd'
+    }
+
+    classpath = sourceSets.main.runtimeClasspath
+    main = 'org.jastadd.ragconnect.compiler.Compiler'
+    args '--o=src/test/02-after-ragconnect/tutorial',
+            'src/test/01-input/tutorial/Test.relast',
+            'src/test/01-input/tutorial/Test.connect',
+            '--rootNode=A',
+            '--protocols=mqtt'
+}
+
+task compileTutorialTest(type: RelastTest) {
+    useJastAddNames = true
+    jastAddList = 'JastAddList'
+    relastFiles 'src/test/02-after-ragconnect/tutorial/Test.relast',
+            'src/test/02-after-ragconnect/tutorial/RagConnect.relast'
+    grammarName = 'src/test/03-after-relast/tutorial/tutorial'
+    packageName = 'tutorial.ast'
+    moreInputFiles 'src/test/01-input/tutorial/Test.jadd',
+            'src/test/02-after-ragconnect/tutorial/MqttHandler.jadd',
+            'src/test/02-after-ragconnect/tutorial/RagConnect.jadd'
+}
+
+compileTestJava.dependsOn compileTutorialTest
+compileTutorialTest.dependsOn preprocessTutorialTest
diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyRead/README.md b/ragconnect.tests/src/test/01-input/defaultOnlyRead/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ee05db648e9204945245cd42ad8d802d2cdab887
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/defaultOnlyRead/README.md
@@ -0,0 +1,3 @@
+# Default Only Read
+
+Idea: Use only `ReadFromMqttDefinition`, test all default mapping definitions from `byte[]` to primitive (and boxed) types
diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.connect b/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..e274994175513ff743409068375f419b6e3141fc
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.connect
@@ -0,0 +1,15 @@
+// --- update definitions ---
+receive NativeTypes.IntValue;
+receive NativeTypes.ShortValue;
+receive NativeTypes.LongValue;
+receive NativeTypes.FloatValue;
+receive NativeTypes.DoubleValue;
+receive NativeTypes.CharValue;
+receive NativeTypes.StringValue;
+
+receive BoxedTypes.IntValue;
+receive BoxedTypes.ShortValue;
+receive BoxedTypes.LongValue;
+receive BoxedTypes.FloatValue;
+receive BoxedTypes.DoubleValue;
+receive BoxedTypes.CharValue;
diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.relast b/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..30eba76b346dace40c72ca4e172b88ef206ea063
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/defaultOnlyRead/Test.relast
@@ -0,0 +1,3 @@
+A ::= NativeTypes* BoxedTypes* ;
+NativeTypes ::= <IntValue:int> <ShortValue:short> <LongValue:long> <FloatValue:float> <DoubleValue:double> <CharValue:char> <StringValue:String> ;
+BoxedTypes ::= <IntValue:Integer> <ShortValue:Short> <LongValue:Long> <FloatValue:Float> <DoubleValue:Double> <CharValue:Character> ;
diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/README.md b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..1bcc8d61a7854b7f9ef4bc1694f4f81caaac2aed
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/README.md
@@ -0,0 +1,3 @@
+# Default Only Write
+
+Idea: Use only `WriteToMqttDefintion`, test all default mapping definitions from primitive (and boxed) types to `byte[]`.
diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.connect b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..a48f49f60f66c3358ee513e79f2bbeb43df8d5e9
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.connect
@@ -0,0 +1,51 @@
+// --- update definitions ---
+// native types, synthesized
+send NativeTypesSyn.IntValue;
+send NativeTypesSyn.ShortValue;
+send NativeTypesSyn.LongValue;
+send NativeTypesSyn.FloatValue;
+send NativeTypesSyn.DoubleValue;
+send NativeTypesSyn.CharValue;
+send NativeTypesSyn.StringValue;
+
+// boxed types, synthesized
+send BoxedTypesSyn.IntValue;
+send BoxedTypesSyn.ShortValue;
+send BoxedTypesSyn.LongValue;
+send BoxedTypesSyn.FloatValue;
+send BoxedTypesSyn.DoubleValue;
+send BoxedTypesSyn.CharValue;
+
+// --- dependency definitions ---
+NativeTypesSyn.IntValue canDependOn NativeTypesSyn.DriverSyn as nativeIntDependency;
+NativeTypesSyn.ShortValue canDependOn NativeTypesSyn.DriverSyn as nativeShortDependency;
+NativeTypesSyn.LongValue canDependOn NativeTypesSyn.DriverSyn as nativeLongDependency;
+NativeTypesSyn.FloatValue canDependOn NativeTypesSyn.DriverSyn as nativeFloatDependency;
+NativeTypesSyn.DoubleValue canDependOn NativeTypesSyn.DriverSyn as nativeDoubleDependency;
+NativeTypesSyn.CharValue canDependOn NativeTypesSyn.DriverSyn as nativeCharDependency;
+NativeTypesSyn.StringValue canDependOn NativeTypesSyn.DriverSyn as nativeStringDependency;
+BoxedTypesSyn.IntValue canDependOn BoxedTypesSyn.DriverSyn as boxedIntDependency;
+BoxedTypesSyn.ShortValue canDependOn BoxedTypesSyn.DriverSyn as boxedShortDependency;
+BoxedTypesSyn.LongValue canDependOn BoxedTypesSyn.DriverSyn as boxedLongDependency;
+BoxedTypesSyn.FloatValue canDependOn BoxedTypesSyn.DriverSyn as boxedFloatDependency;
+BoxedTypesSyn.DoubleValue canDependOn BoxedTypesSyn.DriverSyn as boxedDoubleDependency;
+BoxedTypesSyn.CharValue canDependOn BoxedTypesSyn.DriverSyn as boxedCharDependency;
+
+
+// --- inherited attributes not supported ---
+//// native types, inherited
+//send NativeTypesInh.IntValue;
+//send NativeTypesInh.ShortValue;
+//send NativeTypesInh.LongValue;
+//send NativeTypesInh.FloatValue;
+//send NativeTypesInh.DoubleValue;
+//send NativeTypesInh.CharValue;
+//send NativeTypesInh.StringValue;
+//
+//// boxed types, inherited
+//send BoxedTypesInh.IntValue;
+//send BoxedTypesInh.ShortValue;
+//send BoxedTypesInh.LongValue;
+//send BoxedTypesInh.FloatValue;
+//send BoxedTypesInh.DoubleValue;
+//send BoxedTypesInh.CharValue;
diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.jadd b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..49bbf604d398f7a9d92e879b2b515c9e83dd3182
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.jadd
@@ -0,0 +1,49 @@
+aspect Computation {
+  // native types, synthesized
+  syn int NativeTypesSyn.getIntValue() = Integer.parseInt(getDriverSyn());
+  syn short NativeTypesSyn.getShortValue() = Short.parseShort(getDriverSyn());
+  syn long NativeTypesSyn.getLongValue() = Long.parseLong(getDriverSyn());
+  syn float NativeTypesSyn.getFloatValue() = Float.parseFloat(getDriverSyn());
+  syn double NativeTypesSyn.getDoubleValue() = Double.parseDouble(getDriverSyn());
+  syn char NativeTypesSyn.getCharValue() = getDriverSyn().charAt(0);
+  syn String NativeTypesSyn.getStringValue() = new String(getDriverSyn());
+
+  // boxed types, synthesized
+  syn Integer BoxedTypesSyn.getIntValue() = Integer.valueOf(getDriverSyn());
+  syn Short BoxedTypesSyn.getShortValue() = Short.valueOf(getDriverSyn());
+  syn Long BoxedTypesSyn.getLongValue() = Long.valueOf(getDriverSyn());
+  syn Float BoxedTypesSyn.getFloatValue() = Float.valueOf(getDriverSyn());
+  syn Double BoxedTypesSyn.getDoubleValue() = Double.valueOf(getDriverSyn());
+  syn Character BoxedTypesSyn.getCharValue() = getDriverSyn().charAt(0);
+
+// --- inherited attributes not supported ---
+//  // native types, inherited
+//  inh int NativeTypesInh.getIntValue();
+//  eq A.getNativeTypesInh().getIntValue() = Integer.parseInt(getDriverInh());
+//  inh short NativeTypesInh.getShortValue();
+//  eq A.getNativeTypesInh().getShortValue() = Short.parseShort(getDriverInh());
+//  inh long NativeTypesInh.getLongValue();
+//  eq A.getNativeTypesInh().getLongValue() = Long.parseLong(getDriverInh());
+//  inh float NativeTypesInh.getFloatValue();
+//  eq A.getNativeTypesInh().getFloatValue() = Float.parseFloat(getDriverInh());
+//  inh double NativeTypesInh.getDoubleValue();
+//  eq A.getNativeTypesInh().getDoubleValue() = Double.parseDouble(getDriverInh());
+//  inh char NativeTypesInh.getCharValue();
+//  eq A.getNativeTypesInh().getCharValue() = getDriverInh().charAt(0);
+//  inh String NativeTypesInh.getStringValue();
+//  eq A.getNativeTypesInh().getStringValue() = new String(getDriverInh());
+//
+//  // boxed types, inherited
+//  inh Integer BoxedTypesInh.getIntValue();
+//  eq A.getBoxedTypesInh().getIntValue() = Integer.valueOf(getDriverInh());
+//  inh Short BoxedTypesInh.getShortValue();
+//  eq A.getBoxedTypesInh().getShortValue() = Short.valueOf(getDriverInh());
+//  inh Long BoxedTypesInh.getLongValue();
+//  eq A.getBoxedTypesInh().getLongValue() = Long.valueOf(getDriverInh());
+//  inh Float BoxedTypesInh.getFloatValue();
+//  eq A.getBoxedTypesInh().getFloatValue() = Float.valueOf(getDriverInh());
+//  inh Double BoxedTypesInh.getDoubleValue();
+//  eq A.getBoxedTypesInh().getDoubleValue() = Double.valueOf(getDriverInh());
+//  inh Character BoxedTypesInh.getCharValue();
+//  eq A.getBoxedTypesInh().getCharValue() = getDriverInh().charAt(0);
+}
diff --git a/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.relast b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..e0fd7829b88b8fac04b64393e0262c69b4ac6a62
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/defaultOnlyWrite/Test.relast
@@ -0,0 +1,11 @@
+A ::= NativeTypesSyn* BoxedTypesSyn* <DriverInh:String>;
+// native types, synthesized
+NativeTypesSyn ::= <DriverSyn:String> /<IntValue:int>/ /<ShortValue:short>/ /<LongValue:long>/ /<FloatValue:float>/ /<DoubleValue:double>/ /<CharValue:char>/ /<StringValue:String>/ ;
+// boxed types, synthesized
+BoxedTypesSyn ::= <DriverSyn:String> /<IntValue:Integer>/ /<ShortValue:Short>/ /<LongValue:Long>/ /<FloatValue:Float>/ /<DoubleValue:Double>/ /<CharValue:Character>/ ;
+
+// --- inherited attributes not supported ---
+//// native types, inherited
+//NativeTypesInh ::= /<IntValue:int>/ /<ShortValue:short>/ /<LongValue:long>/ /<FloatValue:float>/ /<DoubleValue:double>/ /<CharValue:char>/ /<StringValue:String>/ ;
+//// boxed types, inherited
+//BoxedTypesInh ::= /<IntValue:Integer>/ /<ShortValue:Short>/ /<LongValue:Long>/ /<FloatValue:Float>/ /<DoubleValue:Double>/ /<CharValue:Character>/ ;
diff --git a/ragconnect.tests/src/test/01-input/errors/Errors.connect b/ragconnect.tests/src/test/01-input/errors/Errors.connect
new file mode 100644
index 0000000000000000000000000000000000000000..bd57822a6c70198cc36698f790fa90655ed4db73
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/errors/Errors.connect
@@ -0,0 +1,72 @@
+// --- update receive definitions ---
+// Error: there must not be two receive definitions for the same token
+receive B.DoubledValue ;
+receive B.DoubledValue using IntToInt ;
+
+// NOT HANDLED \\ Error: the token must be resolvable within the parent type
+// NOT HANDLED \\ receive B.NonExisting ;
+
+// Error: the Token must not be a TokenNTA (i.e., check for !Token.getNTA())
+receive B.ErrorNTA ;
+
+// Error: from-type of first mapping must be byte[] or a supported primitive type
+receive B.ErrorTypeOfFirstMapping using ListToList ;
+
+// Error: to-type of last mapping must be type of the Token
+receive B.ErrorTypeOfLastMapping using StringToList ;
+
+// Error: types of mappings must match (modulo inheritance)
+receive B.ErrorTypeMismatch using StringToList, IntToInt ;
+
+// --- update send definitions ---
+// NOT HANDLED \\ Error: the token must be resolvable within the parent type
+// NOT HANDLED \\ receive C.NonExisting ;
+
+// Error: Token must be a TokenNTA (i.e., check for Token.getNTA())
+send C.ErrorNotNTA ;
+
+// Error: from-type of first mapping must be type of Token
+send C.ErrorTypeOfFirstMapping using IntToInt ;
+
+// Error: to-type of last mapping must be byte[] or a supported primitive type
+send C.ErrorTypeOfLastMapping1 using StringToList ;
+send C.ErrorTypeOfLastMapping2 ;
+
+// Error: types of mappings must match (modulo inheritance)
+send C.ErrorTypeMismatch using StringToList, IntToInt ;
+
+// Error: no more than one send mapping for each TokenComponent
+send C.DoubledValue ;
+send C.DoubledValue using IntToInt ;
+
+// --- dependency definitions ---
+// NOT HANDLED \\ Error: Both, source and target must be resolvable within the parent type
+// NOT HANDLED \\ D.SourceNonExistingTarget canDependOn D.NonExisting as NonExistingTarget ;
+// NOT HANDLED \\ D.NonExisting canDependOn D.TargetNonExistingSource as NonExistingSource ;
+
+// Error: There must be a send update definition for the target token
+D.SourceNoWriteDef canDependOn D.TargetNoWriteDef as NoWriteDef ;
+
+// Error: The name of a dependency definition must not be equal to a list-node on the source
+D.SourceSameAsListNode canDependOn D.TargetSameAsListNode as MyList ;
+send D.TargetSameAsListNode;
+
+// Error: There must not be two dependency definitions with the same name
+D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ;
+D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ;
+send D.TargetDoubledValue;
+
+// --- mapping definitions ---
+ListToList maps java.util.List<String> list to java.util.List<String> {:
+  return list;
+:}
+
+StringToList maps String s to List<String> {:
+  java.util.List<String> result = new java.util.ArrayList<>();
+  result.add(s);
+  return result;
+:}
+
+IntToInt maps int number to int {:
+  return number + 1;
+:}
diff --git a/ragconnect.tests/src/test/01-input/errors/Errors.expected b/ragconnect.tests/src/test/01-input/errors/Errors.expected
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/ragconnect.tests/src/test/01-input/errors/Errors.relast b/ragconnect.tests/src/test/01-input/errors/Errors.relast
new file mode 100644
index 0000000000000000000000000000000000000000..fcde7df1d2e48b9591ddea31aaa3b0a9efdd4cfa
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/errors/Errors.relast
@@ -0,0 +1,15 @@
+A ::= B C D ;
+
+// read definitions
+B ::= /<ErrorNTA:String>/ <ErrorTypeOfFirstMapping:String> <ErrorTypeOfLastMapping:String> <DoubledValue:int> <ErrorTypeMismatch:String> ;
+
+// write definitions
+C ::= <ErrorNotNTA:String> /<ErrorTypeOfFirstMapping:String>/ /<ErrorTypeOfLastMapping1:String>/ /<ErrorTypeOfLastMapping2:List<String>>/ /<ErrorTypeMismatch:String>/ /<DoubledValue:int>/ ;
+
+// dependency definitions
+D ::= <SourceNonExistingTarget>
+      /<TargetNonExistingSource>/
+      <SourceNoWriteDef> /<TargetNoWriteDef>/
+      <SourceSameAsListNode> /<TargetSameAsListNode>/
+      <SourceDoubledValue> /<TargetDoubledValue>/
+      MyList:D* ;
diff --git a/ragconnect.tests/src/test/01-input/errors/README.md b/ragconnect.tests/src/test/01-input/errors/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e8efed33eb3868441a63dc719e3e92993a2c9d49
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/errors/README.md
@@ -0,0 +1,25 @@
+Ideas for errors:
+
+- Read-Update
+    - the token must be resolvable within the parent type
+    - the Token must not be a TokenNTA (i.e., check for `!Token.getNTA()`)
+    - type of first mapping must be `byte[]`
+    - type of last mapping must be type of the Token
+    - types of mappings must match (modulo inheritance)
+- Write-Update
+    - the token must be resolvable within the parent type
+    - Token must be a TokenNTA (i.e., check for `Token.getNTA()`)
+    - type of first mapping must be type of Token
+    - type of last mapping must be `byte[]`
+    - types of mappings must match (modulo inheritance)
+    - no more than one write mapping for each TokenComponent
+- for all type checks, there are three cases regarding the two types to check against:
+    1) both are nonterminal types, check with grammar
+    2) both are known classes, check with `Class.forName()` and subclass-checking-methods
+    3) otherwise issue warning, that types could not be matched
+- dependency-definition
+    - There **must be** a write update definition for the target token
+        - Otherwise there are missing update and write methods used in the virtual setter
+    - Both, source and target must be resolvable within the parent type
+    - The name of a dependency definition must not be equal to a list-node on the source
+    - There must not be two dependency definitions with the same name
diff --git a/ragconnect.tests/src/test/01-input/example/README.md b/ragconnect.tests/src/test/01-input/example/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..7665b437a513602587474904ef08135efee13b44
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/example/README.md
@@ -0,0 +1,3 @@
+# Example
+
+Idea: Use the motivating example from our paper as a test case, including one read, one write update definition
diff --git a/ragconnect.tests/src/test/01-input/example/Test.connect b/ragconnect.tests/src/test/01-input/example/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..2a26051b743145ad8af6ad7b8ea84ad58ce1348f
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/example/Test.connect
@@ -0,0 +1,31 @@
+/* Version 2020-07-15  */
+// --- update definitions ---
+receive Link.CurrentPosition using ParseRobotState, RobotStateToIntPosition ;
+send RobotArm.AppropriateSpeed using CreateSpeedMessage, SerializeRobotConfig ;
+
+// --- dependency definitions ---
+RobotArm.AppropriateSpeed canDependOn Link.CurrentPosition as dependency1 ;
+
+// --- mapping definitions ---
+ParseRobotState maps byte[] bytes to robot.RobotStateOuterClass.RobotState {:
+  TestCounter.INSTANCE.numberParseLinkState += 1;
+  return robot.RobotStateOuterClass.RobotState.parseFrom(bytes);
+:}
+
+SerializeRobotConfig maps config.Config.RobotConfig rc to byte[] {:
+  TestCounter.INSTANCE.numberSerializeRobotConfig += 1;
+  return rc.toByteArray();
+:}
+
+RobotStateToIntPosition maps robot.RobotStateOuterClass.RobotState rs to IntPosition {:
+  TestCounter.INSTANCE.numberLinkStateToIntPosition += 1;
+  robot.RobotStateOuterClass.RobotState.Position p = rs.getPosition();
+  return IntPosition.of((int) (Math.round(p.getX() * 10)), (int) (Math.round(p.getY() * 10)), (int) (Math.round(p.getZ() * 10)));
+:}
+
+CreateSpeedMessage maps double speed to config.Config.RobotConfig {:
+  TestCounter.INSTANCE.numberCreateSpeedMessage += 1;
+  return config.Config.RobotConfig.newBuilder()
+    .setSpeed(speed)
+    .build();
+:}
diff --git a/ragconnect.tests/src/test/01-input/example/Test.jadd b/ragconnect.tests/src/test/01-input/example/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..b62b9aee7cb344c587993feefccdd64ab0bf6e4b
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/example/Test.jadd
@@ -0,0 +1,95 @@
+aspect GrammarTypes {
+  public class TestCounter {
+    public static TestCounter INSTANCE = new TestCounter();
+    public int numberParseLinkState = 0;
+    public int numberSerializeRobotConfig = 0;
+    public int numberLinkStateToIntPosition = 0;
+    public int numberCreateSpeedMessage = 0;
+    public int numberInSafetyZone = 0;
+    public static void reset() {
+      TestCounter.INSTANCE = new TestCounter();
+    }
+  }
+
+  public class IntPosition {
+    private final int x, y, z;
+
+    private IntPosition(int x, int y, int z) {
+      this.x = x;
+      this.y = y;
+      this.z = z;
+    }
+
+    public static IntPosition of(int x, int y, int z) {
+      return new IntPosition(x, y, z);
+    }
+
+    public int getX() {
+      return x;
+    }
+
+    public int getY() {
+      return y;
+    }
+
+    public int getZ() {
+      return z;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      IntPosition that = (IntPosition) o;
+      return x == that.x &&
+          y == that.y &&
+          z == that.z;
+    }
+
+    @Override
+    public int hashCode() {
+      return java.util.Objects.hash(x, y, z);
+    }
+
+    @Override
+    public String toString() {
+      return "(" + x + ", " + y + ", " + z + ")";
+    }
+  }
+
+  inh Model RobotArm.model();
+  eq Model.getRobotArm().model() = this;
+
+  inh RobotArm Link.containingRobotArm();
+  eq RobotArm.getLink().containingRobotArm() = this;
+  eq RobotArm.getEndEffector().containingRobotArm() = this;
+
+  syn boolean RobotArm.isInSafetyZone() {
+    TestCounter.INSTANCE.numberInSafetyZone += 1;
+    for (Link link : getLinkList()) {
+      if (model().getZoneModel().isInSafetyZone(link.getCurrentPosition())) {
+        return true;
+      }
+    }
+    return model().getZoneModel().isInSafetyZone(getEndEffector().getCurrentPosition());
+  }
+
+  cache ZoneModel.isInSafetyZone(IntPosition pos);
+  syn boolean ZoneModel.isInSafetyZone(IntPosition pos) {
+    for (Zone sz : getSafetyZoneList()) {
+      for (Coordinate coordinate : sz.getCoordinateList()) {
+        if (coordinate.getPosition().equals(pos)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  syn double RobotArm.speedLow() = 0.4d;
+  syn double RobotArm.speedHigh() = 1.0d;
+
+  syn double RobotArm.getAppropriateSpeed() {
+    return isInSafetyZone() ? speedLow() : speedHigh();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/example/Test.relast b/ragconnect.tests/src/test/01-input/example/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..b8d932ae470e4bf3dc433f8610eec1f05016e595
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/example/Test.relast
@@ -0,0 +1,13 @@
+Model ::= RobotArm ZoneModel ;
+
+ZoneModel ::= SafetyZone:Zone* ;
+
+Zone ::= Coordinate* ;
+
+RobotArm ::= Link* EndEffector /<AppropriateSpeed:double>/ ;
+
+Link ::= <Name:String> <CurrentPosition:IntPosition> ;
+
+EndEffector : Link;
+
+Coordinate ::= <Position:IntPosition> ;
diff --git a/ragconnect.tests/src/test/01-input/example/config.json b/ragconnect.tests/src/test/01-input/example/config.json
new file mode 100644
index 0000000000000000000000000000000000000000..02843152a51197a04bce4e8e54ad4ec0f703eca5
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/example/config.json
@@ -0,0 +1,9 @@
+{
+  "joints": [
+    {"name": "Joint1", "topic": "robot/arm/joint1"},
+    {"name": "Joint2", "topic": "robot/arm/joint2"},
+    {"name": "EndEffector", "topic": "-", "isEndEffector": true}
+  ],
+  "robotConfigTopic": "robot/config",
+  "dataConfigTopic": "-"
+}
diff --git a/ragconnect.tests/src/test/01-input/read1write2/README.md b/ragconnect.tests/src/test/01-input/read1write2/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..197eeb5b1399a079bd16c9e928622732511f7ca1
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/read1write2/README.md
@@ -0,0 +1,3 @@
+# Read one - Write two
+
+Idea: Define Read-Update for one terminal, add dependencies to two other terminals, which each have Write-Update defined. Test, whether code gets generated correctly and that write is trigger for each of the two terminals upon read.
diff --git a/ragconnect.tests/src/test/01-input/read1write2/Test.connect b/ragconnect.tests/src/test/01-input/read1write2/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..e826f1d040d1668ae5d5d56a83e5b4e5e96a7290
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/read1write2/Test.connect
@@ -0,0 +1,18 @@
+// --- update definitions ---
+// OnSameNonterminal
+receive OnSameNonterminal.Input;
+send OnSameNonterminal.OutInteger;
+send OnSameNonterminal.OutString;
+
+// OnDifferentNonterminal
+receive OnDifferentNonterminal.Input;
+send TheOther.OutInteger;
+send TheOther.OutString;
+
+// --- dependency definitions ---
+// OnSameNonterminal
+OnSameNonterminal.OutInteger canDependOn OnSameNonterminal.Input as IntDependency;
+OnSameNonterminal.OutString canDependOn OnSameNonterminal.Input as StringDependency;
+// OnDifferentNonterminal
+TheOther.OutInteger canDependOn OnDifferentNonterminal.Input as IntDependency;
+TheOther.OutString canDependOn OnDifferentNonterminal.Input as StringDependency;
diff --git a/ragconnect.tests/src/test/01-input/read1write2/Test.jadd b/ragconnect.tests/src/test/01-input/read1write2/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..490772fcc7dcd1f27841c84eaa8a0231851ed6cc
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/read1write2/Test.jadd
@@ -0,0 +1,11 @@
+aspect Computation{
+  // OnSameNonterminal
+  syn int OnSameNonterminal.getOutInteger() = Integer.parseInt(getInput());
+  syn String OnSameNonterminal.getOutString() = "prefix" + getInput();
+
+  // OnDifferentNonterminal
+  syn int TheOther.getOutInteger() = Integer.parseInt(input());
+  syn String TheOther.getOutString() = "prefix" + input();
+  inh String TheOther.input();
+  eq OnDifferentNonterminal.getTheOther().input() = getInput();
+}
diff --git a/ragconnect.tests/src/test/01-input/read1write2/Test.relast b/ragconnect.tests/src/test/01-input/read1write2/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..fa0b171c4b96f097e6812f34434fc60801bc307c
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/read1write2/Test.relast
@@ -0,0 +1,4 @@
+A ::= OnSameNonterminal OnDifferentNonterminal ;
+OnSameNonterminal ::= <Input:String> /<OutInteger:int>/ /<OutString:String>/ ;
+OnDifferentNonterminal ::= <Input:String> TheOther* ;
+TheOther ::= /<OutInteger:int>/ /<OutString:String>/ ;
diff --git a/ragconnect.tests/src/test/01-input/read2write1/Test.connect b/ragconnect.tests/src/test/01-input/read2write1/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..4c2a9b237acb5b42f274a2f095fcb74b4132a49d
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/read2write1/Test.connect
@@ -0,0 +1,18 @@
+// --- update definitions ---
+// OnSameNonterminal
+receive OnSameNonterminal.Input1;
+receive OnSameNonterminal.Input2;
+send OnSameNonterminal.OutInteger;
+
+// OnDifferentNonterminal
+receive OnDifferentNonterminal.Input1;
+receive OnDifferentNonterminal.Input2;
+send TheOther.OutInteger;
+
+// --- dependency definitions ---
+// OnSameNonterminal
+OnSameNonterminal.OutInteger canDependOn OnSameNonterminal.Input1 as Int1Dependency;
+OnSameNonterminal.OutInteger canDependOn OnSameNonterminal.Input2 as Int2Dependency;
+// OnDifferentNonterminal
+TheOther.OutInteger canDependOn OnDifferentNonterminal.Input1 as Int1Dependency;
+TheOther.OutInteger canDependOn OnDifferentNonterminal.Input2 as Int2Dependency;
diff --git a/ragconnect.tests/src/test/01-input/read2write1/Test.jadd b/ragconnect.tests/src/test/01-input/read2write1/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..4d31f1a6441311469f8f93cd150e6c8bb353877e
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/read2write1/Test.jadd
@@ -0,0 +1,11 @@
+aspect Computation{
+  // OnSameNonterminal
+  syn int OnSameNonterminal.getOutInteger() = Integer.parseInt(getInput1() + getInput2());
+
+  // OnDifferentNonterminal
+  syn int TheOther.getOutInteger() = Integer.parseInt(input1() + input2());
+  inh String TheOther.input1();
+  eq OnDifferentNonterminal.getTheOther().input1() = getInput1();
+  inh String TheOther.input2();
+  eq OnDifferentNonterminal.getTheOther().input2() = getInput2();
+}
diff --git a/ragconnect.tests/src/test/01-input/read2write1/Test.relast b/ragconnect.tests/src/test/01-input/read2write1/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..afe0125760afe222c830f383536f6e2f97c8f656
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/read2write1/Test.relast
@@ -0,0 +1,4 @@
+A ::= OnSameNonterminal OnDifferentNonterminal ;
+OnSameNonterminal ::= <Input1:String> <Input2:String> /<OutInteger:int>/ ;
+OnDifferentNonterminal ::= <Input1:String> <Input2:String> TheOther* ;
+TheOther ::= /<OutInteger:int>/ ;
diff --git a/ragconnect.tests/src/test/01-input/tokenValueSend/README.md b/ragconnect.tests/src/test/01-input/tokenValueSend/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..b2d0c3c8ff55186316be2984eb53ffc9f7e42d90
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tokenValueSend/README.md
@@ -0,0 +1,3 @@
+# Token Value Send
+
+Idea: 1) Test normal token send, 2) Test combined receive and send on same token, 3) Test combined receive, send and a dependency
diff --git a/ragconnect.tests/src/test/01-input/tokenValueSend/Test.connect b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..bcc26d1fba55b6241227f8e37e93097ced82284b
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.connect
@@ -0,0 +1,23 @@
+// OnlySend
+send OnlySend.Value using AddPostfix ;
+
+// ReceiveAndSend
+receive ReceiveAndSend.Value using AddPrefix ;
+send ReceiveAndSend.Value using AddPostfix ;
+
+// ReceiveSendAndDepend
+receive ReceiveSendAndDepend.Value using AddPrefix ;
+send ReceiveSendAndDepend.Value using AddPostfix ;
+send ReceiveSendAndDepend.OtherOutput using AddPostfix ;
+
+ReceiveSendAndDepend.OtherOutput canDependOn ReceiveSendAndDepend.Value as dependency1 ;
+
+AddPrefix maps String s to String {:
+  System.out.println("receive " + s);
+  return "Pre-" + s;
+:}
+
+AddPostfix maps String s to String {:
+  System.out.println("send " + s);
+  return s + "-Post";
+:}
diff --git a/ragconnect.tests/src/test/01-input/tokenValueSend/Test.jadd b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..6a2f3e0d01042ff1e30a4b48f6938e6085b4ed0d
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.jadd
@@ -0,0 +1,4 @@
+aspect Computation{
+  // ReceiveSendAndDepend
+  syn String ReceiveSendAndDepend.getOtherOutput() = getValue() + "-T";
+}
diff --git a/ragconnect.tests/src/test/01-input/tokenValueSend/Test.relast b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..eef6095b186555d9660186014f13bdfe68b07bab
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tokenValueSend/Test.relast
@@ -0,0 +1,4 @@
+A ::= OnlySend ReceiveAndSend ReceiveSendAndDepend ;
+OnlySend ::= <Value:String> ;
+ReceiveAndSend ::= <Value:String> ;
+ReceiveSendAndDepend ::= <Value:String> /<OtherOutput:String>/ ;
diff --git a/ragconnect.tests/src/test/01-input/tutorial/README.md b/ragconnect.tests/src/test/01-input/tutorial/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..09995a98b03cf08c364b670b820fedbfd0674cba
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tutorial/README.md
@@ -0,0 +1,3 @@
+# Tutorial
+
+Idea: Test the example from the [documentation](https://jastadd.pages.st.inf.tu-dresden.de/ragconnect/using.html)
diff --git a/ragconnect.tests/src/test/01-input/tutorial/Test.connect b/ragconnect.tests/src/test/01-input/tutorial/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..c6bdc7b3c4f5dcf661ce2ce757301e42eb39a1a7
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tutorial/Test.connect
@@ -0,0 +1,13 @@
+// endpoint definitions
+receive A.Input ;
+send A.OutputOnA ;
+send B.OutputOnB using Transformation ;
+
+// mapping definitions
+Transformation maps String s to String {:
+  return s + "postfix";
+:}
+
+// dependency definitions
+A.OutputOnA canDependOn A.Input as dependencyA ;
+B.OutputOnB canDependOn A.Input as dependencyB ;
diff --git a/ragconnect.tests/src/test/01-input/tutorial/Test.jadd b/ragconnect.tests/src/test/01-input/tutorial/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..4cb9dbf4def3102924b530c58ece410e73fa255b
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tutorial/Test.jadd
@@ -0,0 +1,7 @@
+aspect Computation {
+  syn String A.getOutputOnA() = "a" + getInput();
+
+  syn String B.getOutputOnB() = "b" + input();
+  inh String B.input();
+  eq A.getB().input() = getInput();
+}
diff --git a/ragconnect.tests/src/test/01-input/tutorial/Test.relast b/ragconnect.tests/src/test/01-input/tutorial/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..9009e2787814bb8020d1459fc5d9758f9cc1ca06
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/tutorial/Test.relast
@@ -0,0 +1,2 @@
+A ::= <Input:String> /<OutputOnA:String>/ B* ;
+B ::= /<OutputOnB:String>/ ;
diff --git a/ragconnect.tests/src/test/01-input/via/Test.connect b/ragconnect.tests/src/test/01-input/via/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..e4d2862a651dd6e68f67d2a492b6105a15f0a77c
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/via/Test.connect
@@ -0,0 +1,33 @@
+receive A.Mqtt2MqttInput using MarkMqttInput ;
+receive A.Rest2RestInput using MarkRestInput ;
+receive A.Mqtt2RestInput using MarkMqttInput ;
+receive A.Rest2MqttInput using MarkRestInput ;
+receive A.Both2BothInput ;
+
+send A.Mqtt2MqttOutput using MarkMqttOutput ;
+send A.Rest2RestOutput using MarkRestOutput ;
+send A.Mqtt2RestOutput using MarkRestOutput ;
+send A.Rest2MqttOutput using MarkMqttOutput ;
+send A.Both2RestOutput using MarkRestOutput ;
+send A.Both2MqttOutput using MarkMqttOutput ;
+
+A.Mqtt2MqttOutput canDependOn A.Mqtt2MqttInput as dependencyMqtt2Mqtt ;
+A.Rest2RestOutput canDependOn A.Rest2RestInput as dependencyRest2Rest ;
+A.Mqtt2RestOutput canDependOn A.Mqtt2RestInput as dependencyMqtt2Rest ;
+A.Rest2MqttOutput canDependOn A.Rest2MqttInput as dependencyRest2Mqtt ;
+A.Both2RestOutput canDependOn A.Both2BothInput as dependencyBoth2Rest ;
+A.Both2MqttOutput canDependOn A.Both2BothInput as dependencyBoth2Mqtt ;
+
+MarkMqttInput maps String s to String {:
+  return "FromMqtt-" + s;
+:}
+MarkRestInput maps String s to String {:
+  return "FromRest-" + s;
+:}
+
+MarkMqttOutput maps String s to String {:
+  return s + "-ToMqtt";
+:}
+MarkRestOutput maps String s to String {:
+  return s + "-ToRest";
+:}
diff --git a/ragconnect.tests/src/test/01-input/via/Test.jadd b/ragconnect.tests/src/test/01-input/via/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..9d25387d26e651fe25a937a56a863311b4b7b69a
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/via/Test.jadd
@@ -0,0 +1,8 @@
+aspect Computation {
+  syn lazy String A.getMqtt2MqttOutput() = getMqtt2MqttInput() + "-M2M" ;
+  syn lazy String A.getRest2RestOutput() = getRest2RestInput() + "-R2R" ;
+  syn lazy String A.getMqtt2RestOutput() = getMqtt2RestInput() + "-M2R" ;
+  syn lazy String A.getRest2MqttOutput() = getRest2MqttInput() + "-R2M" ;
+  syn lazy String A.getBoth2MqttOutput() = getBoth2BothInput() + "-B2M" ;
+  syn lazy String A.getBoth2RestOutput() = getBoth2BothInput() + "-B2R" ;
+}
diff --git a/ragconnect.tests/src/test/01-input/via/Test.relast b/ragconnect.tests/src/test/01-input/via/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..1e707fbbc04b1204381351d0c38e826fcbcc8b80
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/via/Test.relast
@@ -0,0 +1,5 @@
+A ::= <Mqtt2MqttInput> /<Mqtt2MqttOutput>/
+      <Rest2RestInput> /<Rest2RestOutput>/
+      <Mqtt2RestInput> /<Mqtt2RestOutput>/
+      <Rest2MqttInput> /<Rest2MqttOutput>/
+      <Both2BothInput> /<Both2MqttOutput>/ /<Both2RestOutput>/;
diff --git a/ragconnect.tests/src/test/02-after-ragconnect/.gitignore b/ragconnect.tests/src/test/02-after-ragconnect/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d
--- /dev/null
+++ b/ragconnect.tests/src/test/02-after-ragconnect/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/ragconnect.tests/src/test/03-after-relast/.gitignore b/ragconnect.tests/src/test/03-after-relast/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d
--- /dev/null
+++ b/ragconnect.tests/src/test/03-after-relast/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/ragconnect.tests/src/test/java-gen/.gitignore b/ragconnect.tests/src/test/java-gen/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d
--- /dev/null
+++ b/ragconnect.tests/src/test/java-gen/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..36baf2cf5ffda1fbb6225daf8f054a40c3a2c331
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractMqttTest.java
@@ -0,0 +1,104 @@
+package org.jastadd.ragconnect.tests;
+
+import defaultOnlyRead.ast.MqttHandler;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base class for tests ensuring running mqtt broker.
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("mqtt")
+public abstract class AbstractMqttTest {
+
+  static boolean checkDone = false;
+  static Boolean checkResult;
+
+  @BeforeAll
+  public static void checkMqttConnection() {
+    if (!checkDone) {
+      checkDone = true;
+      try {
+        checkResult = new MqttHandler()
+            .dontSendWelcomeMessage()
+            .setHost(TestUtils.getMqttHost())
+            .waitUntilReady(2, TimeUnit.SECONDS);
+      } catch (IOException e) {
+        checkResult = false;
+      }
+    }
+    if (!checkResult) {
+      throw new IllegalStateException("Mqtt Broker not ready!");
+    }
+  }
+
+  @Test
+  public final void buildModel() {
+    createModel();
+  }
+
+  @Tag("mqtt")
+  @Test
+  public final void testCommunicateSendInitialValue() throws IOException, InterruptedException {
+    createModel();
+    setupReceiverAndConnect(true);
+
+    communicateSendInitialValue();
+  }
+
+  protected abstract void communicateSendInitialValue() throws InterruptedException;
+
+  @Tag("mqtt")
+  @Test
+  public final void testCommunicateOnlyUpdatedValue() throws IOException, InterruptedException {
+    createModel();
+    setupReceiverAndConnect(false);
+
+    communicateOnlyUpdatedValue();
+  }
+
+  protected abstract void communicateOnlyUpdatedValue() throws InterruptedException;
+
+
+  protected abstract void createModel();
+
+  /**
+   * Begin with this snippet
+   * <pre>
+   *     model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+   *
+   *     handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+   *     assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+   * </pre>
+   *
+   * And then add dependencies, initialise receiver, add connections to those receivers,
+   * and finally call generated connect* methods on model elements.
+   * @param writeCurrentValue
+   */
+  protected abstract void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException;
+
+  @AfterEach
+  public void alwaysCloseConnections() {
+    closeConnections();
+  }
+
+  /**
+   * Write the following snippet (using your correct handler and model):
+   * <pre>
+   * if (handler != null) {
+   *    handler.close();
+   * }
+   * if (model != null) {
+   *   model.ragconnectCloseConnections();
+   * }
+   * </pre>
+   */
+  protected abstract void closeConnections();
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..65302cfa47f0cd83788385ab0d644d2627868a9c
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyReadTest.java
@@ -0,0 +1,138 @@
+package org.jastadd.ragconnect.tests;
+
+import defaultOnlyRead.ast.A;
+import defaultOnlyRead.ast.BoxedTypes;
+import defaultOnlyRead.ast.MqttHandler;
+import defaultOnlyRead.ast.NativeTypes;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "defaultOnlyRead".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class DefaultOnlyReadTest extends AbstractMqttTest {
+
+  private static final String TOPIC_NATIVE_INT = "native/int";
+  private static final String TOPIC_NATIVE_SHORT = "native/short";
+  private static final String TOPIC_NATIVE_LONG = "native/long";
+  private static final String TOPIC_NATIVE_FLOAT = "native/float";
+  private static final String TOPIC_NATIVE_DOUBLE = "native/double";
+  private static final String TOPIC_NATIVE_CHAR = "native/char";
+  private static final String TOPIC_NATIVE_STRING = "native/string";
+
+  private static final String TOPIC_BOXED_INTEGER = "boxed/Integer";
+  private static final String TOPIC_BOXED_SHORT = "boxed/Short";
+  private static final String TOPIC_BOXED_LONG = "boxed/Long";
+  private static final String TOPIC_BOXED_FLOAT = "boxed/Float";
+  private static final String TOPIC_BOXED_DOUBLE = "boxed/Double";
+  private static final String TOPIC_BOXED_CHARACTER = "boxed/Character";
+
+  private A model;
+  private NativeTypes integers;
+  private NativeTypes floats;
+  private NativeTypes chars;
+  private BoxedTypes allBoxed;
+  private MqttHandler sender;
+
+  @Override
+  public void closeConnections() {
+    if (sender != null) {
+      sender.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    integers.connectIntValue(mqttUri(TOPIC_NATIVE_INT));
+    integers.connectShortValue(mqttUri(TOPIC_NATIVE_SHORT));
+    integers.connectLongValue(mqttUri(TOPIC_NATIVE_LONG));
+    floats.connectFloatValue(mqttUri(TOPIC_NATIVE_FLOAT));
+    floats.connectDoubleValue(mqttUri(TOPIC_NATIVE_DOUBLE));
+    chars.connectCharValue(mqttUri(TOPIC_NATIVE_CHAR));
+    chars.connectStringValue(mqttUri(TOPIC_NATIVE_STRING));
+    allBoxed.connectIntValue(mqttUri(TOPIC_BOXED_INTEGER));
+    allBoxed.connectShortValue(mqttUri(TOPIC_BOXED_SHORT));
+    allBoxed.connectLongValue(mqttUri(TOPIC_BOXED_LONG));
+    allBoxed.connectFloatValue(mqttUri(TOPIC_BOXED_FLOAT));
+    allBoxed.connectDoubleValue(mqttUri(TOPIC_BOXED_DOUBLE));
+    allBoxed.connectCharValue(mqttUri(TOPIC_BOXED_CHARACTER));
+
+    sender = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(sender.waitUntilReady(2, TimeUnit.SECONDS));
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    final int expectedIntValue = 1;
+    final short expectedShortValue = 2;
+    final long expectedLongValue = 3L;
+    final float expectedFloatValue = 4.1f;
+    final double expectedDoubleValue = 5.2;
+    final char expectedCharValue = 'c';
+    final String expectedStringValue = "6.3";
+
+    sender.publish(TOPIC_NATIVE_INT, ByteBuffer.allocate(4).putInt(expectedIntValue).array());
+    sender.publish(TOPIC_NATIVE_SHORT, ByteBuffer.allocate(2).putShort(expectedShortValue).array());
+    sender.publish(TOPIC_NATIVE_LONG, ByteBuffer.allocate(8).putLong(expectedLongValue).array());
+    sender.publish(TOPIC_NATIVE_FLOAT, ByteBuffer.allocate(4).putFloat(expectedFloatValue).array());
+    sender.publish(TOPIC_NATIVE_DOUBLE, ByteBuffer.allocate(8).putDouble(expectedDoubleValue).array());
+    sender.publish(TOPIC_NATIVE_CHAR, ByteBuffer.allocate(2).putChar(expectedCharValue).array());
+    sender.publish(TOPIC_NATIVE_STRING, expectedStringValue.getBytes());
+
+    sender.publish(TOPIC_BOXED_INTEGER, ByteBuffer.allocate(4).putInt(expectedIntValue).array());
+    sender.publish(TOPIC_BOXED_SHORT, ByteBuffer.allocate(2).putShort(expectedShortValue).array());
+    sender.publish(TOPIC_BOXED_LONG, ByteBuffer.allocate(8).putLong(expectedLongValue).array());
+    sender.publish(TOPIC_BOXED_FLOAT, ByteBuffer.allocate(4).putFloat(expectedFloatValue).array());
+    sender.publish(TOPIC_BOXED_DOUBLE, ByteBuffer.allocate(8).putDouble(expectedDoubleValue).array());
+    sender.publish(TOPIC_BOXED_CHARACTER, ByteBuffer.allocate(2).putChar(expectedCharValue).array());
+
+    TestUtils.waitForMqtt();
+
+    assertEquals(expectedIntValue, integers.getIntValue());
+    assertEquals(expectedShortValue, integers.getShortValue());
+    assertEquals(expectedLongValue, integers.getLongValue());
+    assertEquals(expectedFloatValue, floats.getFloatValue(), TestUtils.DELTA);
+    assertEquals(expectedDoubleValue, floats.getDoubleValue(), TestUtils.DELTA);
+    assertEquals(expectedCharValue, chars.getCharValue());
+    assertEquals(expectedStringValue, chars.getStringValue());
+
+    assertEquals(expectedIntValue, allBoxed.getIntValue().intValue());
+    assertEquals(expectedShortValue, allBoxed.getShortValue().shortValue());
+    assertEquals(expectedLongValue, allBoxed.getLongValue().longValue());
+    assertEquals(expectedFloatValue, allBoxed.getFloatValue(), TestUtils.DELTA);
+    assertEquals(expectedDoubleValue, allBoxed.getDoubleValue(), TestUtils.DELTA);
+    assertEquals(expectedCharValue, allBoxed.getCharValue().charValue());
+  }
+
+  @Override
+  protected void communicateSendInitialValue() {
+    // empty
+  }
+
+  @Override
+  protected void createModel() {
+    model = new A();
+    integers = new NativeTypes();
+    model.addNativeTypes(integers);
+    floats = new NativeTypes();
+    model.addNativeTypes(floats);
+    chars = new NativeTypes();
+    model.addNativeTypes(chars);
+    allBoxed = new BoxedTypes();
+    model.addBoxedTypes(allBoxed);
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c312d4c534d624f0a6c2ee891eba36ddb63be584
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/DefaultOnlyWriteTest.java
@@ -0,0 +1,310 @@
+package org.jastadd.ragconnect.tests;
+
+import defaultOnlyWrite.ast.A;
+import defaultOnlyWrite.ast.BoxedTypesSyn;
+import defaultOnlyWrite.ast.MqttHandler;
+import defaultOnlyWrite.ast.NativeTypesSyn;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test case "defaultOnlyRead".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class DefaultOnlyWriteTest extends AbstractMqttTest {
+
+  private static final String TOPIC_NATIVE_INT = "native/int";
+  private static final String TOPIC_NATIVE_SHORT = "native/short";
+  private static final String TOPIC_NATIVE_LONG = "native/long";
+  private static final String TOPIC_NATIVE_FLOAT = "native/float";
+  private static final String TOPIC_NATIVE_DOUBLE = "native/double";
+  private static final String TOPIC_NATIVE_CHAR = "native/char";
+  private static final String TOPIC_NATIVE_STRING = "native/string";
+
+  private static final String TOPIC_BOXED_INTEGER = "boxed/Integer";
+  private static final String TOPIC_BOXED_SHORT = "boxed/Short";
+  private static final String TOPIC_BOXED_LONG = "boxed/Long";
+  private static final String TOPIC_BOXED_FLOAT = "boxed/Float";
+  private static final String TOPIC_BOXED_DOUBLE = "boxed/Double";
+  private static final String TOPIC_BOXED_CHARACTER = "boxed/Character";
+
+  private A model;
+  private NativeTypesSyn nativeIntegers;
+  private NativeTypesSyn nativeFloats;
+  private NativeTypesSyn nativeChars;
+  private BoxedTypesSyn boxedIntegers;
+  private BoxedTypesSyn boxedFloats;
+  private BoxedTypesSyn boxedChars;
+  private MqttHandler receiver;
+  private ReceiverData data;
+
+  @Override
+  public void closeConnections() {
+    if (receiver != null) {
+      receiver.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+
+
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    // check initial value
+    TestUtils.waitForMqtt();
+    checkData(1, 1, 1.1, 'a', "ab");
+
+    // set new value
+    setData("2", "2.2", "cd");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(2, 2, 2.2, 'c', "cd");
+
+    // set new value
+    setData("3", "3.2", "ee");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(3, 3, 3.2, 'e', "ee");
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    // check initial value (will be default values)
+    TestUtils.waitForMqtt();
+    checkData(0, null, null, null, null);
+
+    // set new value
+    setData("2", "2.2", "cd");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(1, 2, 2.2, 'c', "cd");
+
+    // set new value
+    setData("3", "3.2", "ee");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(2, 3, 3.2, 'e', "ee");
+  }
+
+  @Override
+  protected void createModel() {
+    model = new A();
+
+    nativeIntegers = new NativeTypesSyn();
+    nativeFloats = new NativeTypesSyn();
+    nativeChars = new NativeTypesSyn();
+    model.addNativeTypesSyn(nativeIntegers);
+    model.addNativeTypesSyn(nativeFloats);
+    model.addNativeTypesSyn(nativeChars);
+
+    boxedIntegers = new BoxedTypesSyn();
+    boxedFloats = new BoxedTypesSyn();
+    boxedChars = new BoxedTypesSyn();
+    model.addBoxedTypesSyn(boxedIntegers);
+    model.addBoxedTypesSyn(boxedFloats);
+    model.addBoxedTypesSyn(boxedChars);
+
+    setData("1", "1.1", "ab");
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    receiver = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(receiver.waitUntilReady(2, TimeUnit.SECONDS));
+
+    nativeIntegers.addNativeIntDependency(nativeIntegers);
+    nativeIntegers.addNativeShortDependency(nativeIntegers);
+    nativeIntegers.addNativeLongDependency(nativeIntegers);
+    nativeFloats.addNativeFloatDependency(nativeFloats);
+    nativeFloats.addNativeDoubleDependency(nativeFloats);
+    nativeChars.addNativeCharDependency(nativeChars);
+    nativeChars.addNativeStringDependency(nativeChars);
+
+    boxedIntegers.addBoxedIntDependency(boxedIntegers);
+    boxedIntegers.addBoxedShortDependency(boxedIntegers);
+    boxedIntegers.addBoxedLongDependency(boxedIntegers);
+    boxedFloats.addBoxedFloatDependency(boxedFloats);
+    boxedFloats.addBoxedDoubleDependency(boxedFloats);
+    boxedChars.addBoxedCharDependency(boxedChars);
+
+    data = new ReceiverData();
+
+    receiver.newConnection(TOPIC_NATIVE_INT, bytes -> {
+      data.numberOfNativeIntValues += 1;
+      data.lastNativeIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+    });
+    receiver.newConnection(TOPIC_NATIVE_SHORT, bytes -> {
+      data.numberOfNativeShortValues += 1;
+      data.lastNativeShortValue = java.nio.ByteBuffer.wrap(bytes).getShort();
+    });
+    receiver.newConnection(TOPIC_NATIVE_LONG, bytes -> {
+      data.numberOfNativeLongValues += 1;
+      data.lastNativeLongValue = java.nio.ByteBuffer.wrap(bytes).getLong();
+    });
+    receiver.newConnection(TOPIC_NATIVE_FLOAT, bytes -> {
+      data.numberOfNativeFloatValues += 1;
+      data.lastNativeFloatValue = java.nio.ByteBuffer.wrap(bytes).getFloat();
+    });
+    receiver.newConnection(TOPIC_NATIVE_DOUBLE, bytes -> {
+      data.numberOfNativeDoubleValues += 1;
+      data.lastNativeDoubleValue = java.nio.ByteBuffer.wrap(bytes).getDouble();
+    });
+    receiver.newConnection(TOPIC_NATIVE_CHAR, bytes -> {
+      data.numberOfNativeCharValues += 1;
+      data.lastNativeCharValue = java.nio.ByteBuffer.wrap(bytes).getChar();
+    });
+    receiver.newConnection(TOPIC_NATIVE_STRING, bytes -> {
+      data.numberOfNativeStringValues += 1;
+      data.lastNativeStringValue = new String(bytes);
+    });
+    receiver.newConnection(TOPIC_BOXED_INTEGER, bytes -> {
+      data.numberOfBoxedIntValues += 1;
+      data.lastBoxedIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+    });
+    receiver.newConnection(TOPIC_BOXED_SHORT, bytes -> {
+      data.numberOfBoxedShortValues += 1;
+      data.lastBoxedShortValue = java.nio.ByteBuffer.wrap(bytes).getShort();
+    });
+    receiver.newConnection(TOPIC_BOXED_LONG, bytes -> {
+      data.numberOfBoxedLongValues += 1;
+      data.lastBoxedLongValue = java.nio.ByteBuffer.wrap(bytes).getLong();
+    });
+    receiver.newConnection(TOPIC_BOXED_FLOAT, bytes -> {
+      data.numberOfBoxedFloatValues += 1;
+      data.lastBoxedFloatValue = java.nio.ByteBuffer.wrap(bytes).getFloat();
+    });
+    receiver.newConnection(TOPIC_BOXED_DOUBLE, bytes -> {
+      data.numberOfBoxedDoubleValues += 1;
+      data.lastBoxedDoubleValue = java.nio.ByteBuffer.wrap(bytes).getDouble();
+    });
+    receiver.newConnection(TOPIC_BOXED_CHARACTER, bytes -> {
+      data.numberOfBoxedCharValues += 1;
+      data.lastBoxedCharValue = java.nio.ByteBuffer.wrap(bytes).getChar();
+    });
+
+    nativeIntegers.connectIntValue(mqttUri(TOPIC_NATIVE_INT), writeCurrentValue);
+    nativeIntegers.connectShortValue(mqttUri(TOPIC_NATIVE_SHORT), writeCurrentValue);
+    nativeIntegers.connectLongValue(mqttUri(TOPIC_NATIVE_LONG), writeCurrentValue);
+    nativeFloats.connectFloatValue(mqttUri(TOPIC_NATIVE_FLOAT), writeCurrentValue);
+    nativeFloats.connectDoubleValue(mqttUri(TOPIC_NATIVE_DOUBLE), writeCurrentValue);
+    nativeChars.connectCharValue(mqttUri(TOPIC_NATIVE_CHAR), writeCurrentValue);
+    nativeChars.connectStringValue(mqttUri(TOPIC_NATIVE_STRING), writeCurrentValue);
+    boxedIntegers.connectIntValue(mqttUri(TOPIC_BOXED_INTEGER), writeCurrentValue);
+    boxedIntegers.connectShortValue(mqttUri(TOPIC_BOXED_SHORT), writeCurrentValue);
+    boxedIntegers.connectLongValue(mqttUri(TOPIC_BOXED_LONG), writeCurrentValue);
+    boxedFloats.connectFloatValue(mqttUri(TOPIC_BOXED_FLOAT), writeCurrentValue);
+    boxedFloats.connectDoubleValue(mqttUri(TOPIC_BOXED_DOUBLE), writeCurrentValue);
+    boxedChars.connectCharValue(mqttUri(TOPIC_BOXED_CHARACTER), writeCurrentValue);
+  }
+
+  private void setData(String integerDriver, String floatDriver, String stringDriver) {
+    nativeIntegers.setDriverSyn(integerDriver);
+    nativeFloats.setDriverSyn(floatDriver);
+    nativeChars.setDriverSyn(stringDriver);
+
+    boxedIntegers.setDriverSyn(integerDriver);
+    boxedFloats.setDriverSyn(floatDriver);
+    boxedChars.setDriverSyn(stringDriver);
+  }
+
+  private void checkData(int expectedNumberOfValues,
+                         Integer expectedInt, Double expectedDouble,
+                         Character expectedChar, String expectedString) {
+    assertEquals(expectedNumberOfValues, data.numberOfNativeIntValues);
+    assertEquals(expectedNumberOfValues, data.numberOfNativeShortValues);
+    assertEquals(expectedNumberOfValues, data.numberOfNativeLongValues);
+    assertEquals(expectedNumberOfValues, data.numberOfNativeFloatValues);
+    assertEquals(expectedNumberOfValues, data.numberOfNativeDoubleValues);
+    assertEquals(expectedNumberOfValues, data.numberOfNativeCharValues);
+    assertEquals(expectedNumberOfValues, data.numberOfNativeStringValues);
+
+    assertEquals(expectedNumberOfValues, data.numberOfBoxedIntValues);
+    assertEquals(expectedNumberOfValues, data.numberOfBoxedShortValues);
+    assertEquals(expectedNumberOfValues, data.numberOfBoxedLongValues);
+    assertEquals(expectedNumberOfValues, data.numberOfBoxedFloatValues);
+    assertEquals(expectedNumberOfValues, data.numberOfBoxedDoubleValues);
+    assertEquals(expectedNumberOfValues, data.numberOfBoxedCharValues);
+
+    if (expectedInt != null) {
+      assertEquals(expectedInt.intValue(), data.lastNativeIntValue);
+      assertEquals(expectedInt.shortValue(), data.lastNativeShortValue);
+      assertEquals(expectedInt.longValue(), data.lastNativeLongValue);
+      assertEquals(expectedInt.intValue(), data.lastBoxedIntValue.intValue());
+      assertEquals(expectedInt.shortValue(), data.lastBoxedShortValue.shortValue());
+      assertEquals(expectedInt.longValue(), data.lastBoxedLongValue.longValue());
+    } else {
+      assertEquals(0, data.lastNativeIntValue);
+      assertEquals(0, data.lastNativeShortValue);
+      assertEquals(0, data.lastNativeLongValue);
+      assertNull(data.lastBoxedIntValue);
+      assertNull(data.lastBoxedShortValue);
+      assertNull(data.lastBoxedLongValue);
+    }
+
+    if (expectedDouble != null) {
+      assertEquals(expectedDouble.floatValue(), data.lastNativeFloatValue, TestUtils.DELTA);
+      assertEquals(expectedDouble, data.lastNativeDoubleValue, TestUtils.DELTA);
+      assertEquals(expectedDouble.floatValue(), data.lastBoxedFloatValue, TestUtils.DELTA);
+      assertEquals(expectedDouble, data.lastBoxedDoubleValue, TestUtils.DELTA);
+    } else {
+      assertEquals(0f, data.lastNativeFloatValue, TestUtils.DELTA);
+      assertEquals(0d, data.lastNativeDoubleValue, TestUtils.DELTA);
+      assertNull(data.lastBoxedFloatValue);
+      assertNull(data.lastBoxedDoubleValue);
+    }
+
+    if (expectedChar != null) {
+      assertEquals(expectedChar.charValue(), data.lastNativeCharValue);
+      assertEquals(expectedChar, data.lastBoxedCharValue);
+    } else {
+      assertEquals('\0', data.lastNativeCharValue);
+      assertNull(data.lastBoxedCharValue);
+    }
+    assertEquals(expectedString, data.lastNativeStringValue);
+  }
+
+  private static class ReceiverData {
+    int lastNativeIntValue;
+    int numberOfNativeIntValues = 0;
+    short lastNativeShortValue;
+    int numberOfNativeShortValues = 0;
+    long lastNativeLongValue;
+    int numberOfNativeLongValues = 0;
+    float lastNativeFloatValue;
+    int numberOfNativeFloatValues = 0;
+    double lastNativeDoubleValue;
+    int numberOfNativeDoubleValues = 0;
+    char lastNativeCharValue;
+    int numberOfNativeCharValues = 0;
+    String lastNativeStringValue;
+    int numberOfNativeStringValues = 0;
+
+    Integer lastBoxedIntValue;
+    int numberOfBoxedIntValues = 0;
+    Short lastBoxedShortValue;
+    int numberOfBoxedShortValues = 0;
+    Long lastBoxedLongValue;
+    int numberOfBoxedLongValues = 0;
+    Float lastBoxedFloatValue;
+    int numberOfBoxedFloatValues = 0;
+    Double lastBoxedDoubleValue;
+    int numberOfBoxedDoubleValues = 0;
+    Character lastBoxedCharValue;
+    int numberOfBoxedCharValues = 0;
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
new file mode 100644
index 0000000000000000000000000000000000000000..17d853fba71c2ee46c6c6c7b5424958af0a5f8d2
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Errors.java
@@ -0,0 +1,84 @@
+package org.jastadd.ragconnect.tests;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jastadd.ragconnect.compiler.Compiler;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.jastadd.ragconnect.tests.TestUtils.exec;
+import static org.jastadd.ragconnect.tests.TestUtils.readFile;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class Errors {
+
+  private static final Logger logger = LogManager.getLogger(Errors.class);
+  private static final String FILENAME_PATTERN = "$FILENAME";
+  private static final String INPUT_DIRECTORY = "./src/test/01-input/errors/";
+  private static final String OUTPUT_DIRECTORY = "./src/test/02-after-ragconnect/errors/";
+
+  @BeforeAll
+  public static void createOutputDirectory() {
+    File outputDirectory = new File(OUTPUT_DIRECTORY);
+    assertTrue((outputDirectory.exists() && outputDirectory.isDirectory()) || outputDirectory.mkdir());
+  }
+
+  @Test
+  void testStandardErrors() throws IOException {
+    test("Errors", "A");
+  }
+
+  @SuppressWarnings("SameParameterValue")
+  private void test(String name, String rootNode) throws IOException {
+    String grammarFile = INPUT_DIRECTORY + name + ".relast";
+    String ragconnectFile = INPUT_DIRECTORY + name + ".connect";
+    String outFile = OUTPUT_DIRECTORY + name + ".out";
+    String expectedFile = INPUT_DIRECTORY + name + ".expected";
+
+    try {
+      logger.debug("user.dir: {}", System.getProperty("user.dir"));
+      String[] args = {
+          "--o=" + OUTPUT_DIRECTORY,
+          grammarFile,
+          ragconnectFile,
+          "--rootNode=" + rootNode,
+          "--verbose"
+      };
+      int returnValue = exec(Compiler.class, args, new File(outFile));
+      Assertions.assertEquals(1, returnValue, "RagConnect did not return with value 1");
+    } catch (IOException | InterruptedException e) {
+      e.printStackTrace();
+    }
+
+    String out = readFile(outFile, Charset.defaultCharset());
+    String expected = readFile(expectedFile, Charset.defaultCharset());
+//    if (inFiles.size() == 1) {
+      expected = expected.replace(FILENAME_PATTERN, name);
+//    } else {
+//      for (int i = 0; i < inFiles.size(); i++) {
+//        expected = expected.replace(FILENAME_PATTERN + (i + 1), inFiles.get(i));
+//      }
+//    }
+    List<String> outList = Arrays.asList(out.split("\n"));
+    Collections.sort(outList);
+    List<String> expectedList = Arrays.stream(expected.split("\n"))
+        .sorted()
+        .filter(s -> !s.isEmpty() && !s.startsWith("//"))
+        .collect(Collectors.toList());
+
+    // FIXME errors not handled correctly at the moment
+//    Assertions.assertLinesMatch(expectedList, outList);
+
+    logger.info("ragconnect for " + name + " returned:\n{}", out);
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3df78d1b17658a1327afa0f4e2f308eb5ee159b
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ExampleTest.java
@@ -0,0 +1,278 @@
+package org.jastadd.ragconnect.tests;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import config.Config.RobotConfig;
+import example.ast.*;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import robot.RobotStateOuterClass.RobotState;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test case "example".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class ExampleTest extends AbstractMqttTest {
+
+  private static final String TOPIC_CONFIG = "robot/config";
+  private static final String TOPIC_JOINT1 = "robot/arm/joint1";
+  private static final String TOPIC_JOINT2 = "robot/arm/joint2";
+
+  private Model model;
+  private RobotArm robotArm;
+  private Link link1;
+  private Link link2;
+  private MqttHandler handler;
+  private ReceiverData data;
+
+  @BeforeEach
+  public void resetTestCounter() {
+    TestCounter.reset();
+  }
+
+  @Override
+  public void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    // joint is currently within the safety zone, so speed should be low
+    TestUtils.waitForMqtt();
+    assertEquals(0, TestCounter.INSTANCE.numberParseLinkState);
+    assertEquals(0, TestCounter.INSTANCE.numberLinkStateToIntPosition);
+    assertEquals(1, TestCounter.INSTANCE.numberInSafetyZone);
+    assertEquals(1, TestCounter.INSTANCE.numberCreateSpeedMessage);
+    assertEquals(1, TestCounter.INSTANCE.numberSerializeRobotConfig);
+    assertEquals(1, data.numberOfConfigs);
+    assertFalse(data.failedLastConversion);
+    assertEquals(robotArm.speedLow(), data.lastConfig.getSpeed(), TestUtils.DELTA);
+
+    // change position of the first joint out of the safety zone, second still in
+    sendData(TOPIC_JOINT1, 0.2f, 0.2f, 0.2f);
+
+    // still in safety zone, so no update should have been sent
+    TestUtils.waitForMqtt();
+    assertEquals(makePosition(2, 2, 2), link1.getCurrentPosition());
+    assertEquals(1, TestCounter.INSTANCE.numberParseLinkState);
+    assertEquals(1, TestCounter.INSTANCE.numberLinkStateToIntPosition);
+    assertEquals(2, TestCounter.INSTANCE.numberInSafetyZone);
+    assertEquals(2, TestCounter.INSTANCE.numberCreateSpeedMessage);
+    assertEquals(2, TestCounter.INSTANCE.numberSerializeRobotConfig);
+    assertEquals(1, data.numberOfConfigs);
+    assertFalse(data.failedLastConversion);
+    assertEquals(robotArm.speedLow(), data.lastConfig.getSpeed(), TestUtils.DELTA);
+
+    // change position of second joint also out of the safety zone, now speed must be high
+    sendData(TOPIC_JOINT2, 0.3f, 0.4f, 0.5f);
+
+    TestUtils.waitForMqtt();
+    assertEquals(makePosition(3, 4, 5), link2.getCurrentPosition());
+    assertEquals(2, TestCounter.INSTANCE.numberParseLinkState);
+    assertEquals(2, TestCounter.INSTANCE.numberLinkStateToIntPosition);
+    assertEquals(3, TestCounter.INSTANCE.numberInSafetyZone);
+    assertEquals(3, TestCounter.INSTANCE.numberCreateSpeedMessage);
+    assertEquals(3, TestCounter.INSTANCE.numberSerializeRobotConfig);
+    assertEquals(2, data.numberOfConfigs);
+    assertFalse(data.failedLastConversion);
+    assertEquals(robotArm.speedHigh(), data.lastConfig.getSpeed(), TestUtils.DELTA);
+
+    // change position of second joint, no change after mapping
+    sendData(TOPIC_JOINT2, 0.33f, 0.42f, 0.51f);
+
+    TestUtils.waitForMqtt();
+    assertEquals(makePosition(3, 4, 5), link2.getCurrentPosition());
+    assertEquals(3, TestCounter.INSTANCE.numberParseLinkState);
+    assertEquals(3, TestCounter.INSTANCE.numberLinkStateToIntPosition);
+    assertEquals(3, TestCounter.INSTANCE.numberInSafetyZone);
+    assertEquals(3, TestCounter.INSTANCE.numberCreateSpeedMessage);
+    assertEquals(3, TestCounter.INSTANCE.numberSerializeRobotConfig);
+    assertEquals(2, data.numberOfConfigs);
+    assertFalse(data.failedLastConversion);
+
+    // change position of second joint, still out of the safety zone, no update should be sent
+    sendData(TOPIC_JOINT2, 1.3f, 2.4f, 3.5f);
+
+    TestUtils.waitForMqtt();
+    assertEquals(makePosition(13, 24, 35), link2.getCurrentPosition());
+    assertEquals(4, TestCounter.INSTANCE.numberParseLinkState);
+    assertEquals(4, TestCounter.INSTANCE.numberLinkStateToIntPosition);
+    assertEquals(4, TestCounter.INSTANCE.numberInSafetyZone);
+    assertEquals(4, TestCounter.INSTANCE.numberCreateSpeedMessage);
+    assertEquals(4, TestCounter.INSTANCE.numberSerializeRobotConfig);
+    assertEquals(2, data.numberOfConfigs);
+    assertFalse(data.failedLastConversion);
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+// no value should have been sent
+    TestUtils.waitForMqtt();
+    assertEquals(0, TestCounter.INSTANCE.numberParseLinkState);
+    assertEquals(0, TestCounter.INSTANCE.numberLinkStateToIntPosition);
+    assertEquals(1, TestCounter.INSTANCE.numberInSafetyZone);
+    assertEquals(1, TestCounter.INSTANCE.numberCreateSpeedMessage);
+    assertEquals(1, TestCounter.INSTANCE.numberSerializeRobotConfig);
+    assertEquals(0, data.numberOfConfigs);
+
+    // change position of the first joint out of the safety zone, second still in
+    sendData(TOPIC_JOINT1, 0.2f, 0.2f, 0.2f);
+
+    // still in safety zone, hence, no value should have been sent
+    TestUtils.waitForMqtt();
+    assertEquals(makePosition(2, 2, 2), link1.getCurrentPosition());
+    assertEquals(1, TestCounter.INSTANCE.numberParseLinkState);
+    assertEquals(1, TestCounter.INSTANCE.numberLinkStateToIntPosition);
+    assertEquals(2, TestCounter.INSTANCE.numberInSafetyZone);
+    assertEquals(2, TestCounter.INSTANCE.numberCreateSpeedMessage);
+    assertEquals(2, TestCounter.INSTANCE.numberSerializeRobotConfig);
+    assertEquals(0, data.numberOfConfigs);
+
+    // change position of second joint also out of the safety zone, now speed must be high
+    sendData(TOPIC_JOINT2, 0.3f, 0.4f, 0.5f);
+
+    TestUtils.waitForMqtt();
+    assertEquals(makePosition(3, 4, 5), link2.getCurrentPosition());
+    assertEquals(2, TestCounter.INSTANCE.numberParseLinkState);
+    assertEquals(2, TestCounter.INSTANCE.numberLinkStateToIntPosition);
+    assertEquals(3, TestCounter.INSTANCE.numberInSafetyZone);
+    assertEquals(3, TestCounter.INSTANCE.numberCreateSpeedMessage);
+    assertEquals(3, TestCounter.INSTANCE.numberSerializeRobotConfig);
+    assertEquals(1, data.numberOfConfigs);
+    assertFalse(data.failedLastConversion);
+    assertEquals(robotArm.speedHigh(), data.lastConfig.getSpeed(), TestUtils.DELTA);
+
+    // change position of second joint, no change after mapping
+    sendData(TOPIC_JOINT2, 0.33f, 0.42f, 0.51f);
+
+    TestUtils.waitForMqtt();
+    assertEquals(makePosition(3, 4, 5), link2.getCurrentPosition());
+    assertEquals(3, TestCounter.INSTANCE.numberParseLinkState);
+    assertEquals(3, TestCounter.INSTANCE.numberLinkStateToIntPosition);
+    assertEquals(3, TestCounter.INSTANCE.numberInSafetyZone);
+    assertEquals(3, TestCounter.INSTANCE.numberCreateSpeedMessage);
+    assertEquals(3, TestCounter.INSTANCE.numberSerializeRobotConfig);
+    assertEquals(1, data.numberOfConfigs);
+    assertFalse(data.failedLastConversion);
+
+    // change position of second joint, still out of the safety zone, no update should be sent
+    sendData(TOPIC_JOINT2, 1.3f, 2.4f, 3.5f);
+
+    TestUtils.waitForMqtt();
+    assertEquals(makePosition(13, 24, 35), link2.getCurrentPosition());
+    assertEquals(4, TestCounter.INSTANCE.numberParseLinkState);
+    assertEquals(4, TestCounter.INSTANCE.numberLinkStateToIntPosition);
+    assertEquals(4, TestCounter.INSTANCE.numberInSafetyZone);
+    assertEquals(4, TestCounter.INSTANCE.numberCreateSpeedMessage);
+    assertEquals(4, TestCounter.INSTANCE.numberSerializeRobotConfig);
+    assertEquals(1, data.numberOfConfigs);
+    assertFalse(data.failedLastConversion);
+  }
+
+  @Test
+  public void testFailedConversion() throws IOException {
+    createModel();
+    setupReceiverAndConnect(false);
+
+    handler.publish(TOPIC_JOINT1, "not-a-pandaLinkState".getBytes());
+    assertEquals(0, data.numberOfConfigs);
+    assertTrue(data.failedLastConversion);
+  }
+
+  private void sendData(String topic, float x, float y, float z) {
+    handler.publish(topic, RobotState.newBuilder()
+        .setPosition(RobotState.Position.newBuilder().setX(x).setY(y).setZ(z).build())
+        .build()
+        .toByteArray()
+    );
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    // add dependencies
+    robotArm.addDependency1(link1);
+    robotArm.addDependency1(link2);
+    robotArm.addDependency1(robotArm.getEndEffector());
+
+    data = new ReceiverData();
+
+    handler.newConnection(TOPIC_CONFIG, bytes -> {
+      data.numberOfConfigs += 1;
+      try {
+        data.lastConfig = RobotConfig.parseFrom(bytes);
+        data.failedLastConversion = false;
+      } catch (InvalidProtocolBufferException e) {
+        data.failedLastConversion = true;
+      }
+    });
+
+    robotArm.connectAppropriateSpeed(mqttUri(TOPIC_CONFIG), writeCurrentValue);
+    link1.connectCurrentPosition(mqttUri(TOPIC_JOINT1));
+    link2.connectCurrentPosition(mqttUri(TOPIC_JOINT2));
+  }
+
+  @Override
+  protected void createModel() {
+    model = new Model();
+
+    ZoneModel zoneModel = new ZoneModel();
+
+    IntPosition firstPosition = makePosition(0, 0, 0);
+    IntPosition secondPosition = makePosition(-1, 0, 0);
+    IntPosition thirdPosition = makePosition(1, 0, 0);
+
+    Zone safetyZone = new Zone();
+    safetyZone.addCoordinate(new Coordinate(firstPosition));
+    safetyZone.addCoordinate(new Coordinate(secondPosition));
+    safetyZone.addCoordinate(new Coordinate(thirdPosition));
+    zoneModel.addSafetyZone(safetyZone);
+    model.setZoneModel(zoneModel);
+
+    robotArm = new RobotArm();
+
+    link1 = new Link();
+    link1.setName("joint1");
+    link1.setCurrentPosition(firstPosition);
+
+    link2 = new Link();
+    link2.setName("joint2");
+    link2.setCurrentPosition(secondPosition);
+
+    EndEffector endEffector = new EndEffector();
+    endEffector.setName("gripper");
+    endEffector.setCurrentPosition(makePosition(2, 2, 3));
+
+    robotArm.addLink(link1);
+    robotArm.addLink(link2);
+    robotArm.setEndEffector(endEffector);
+    model.setRobotArm(robotArm);
+  }
+
+  private static IntPosition makePosition(int x, int y, int z) {
+    return IntPosition.of(x, y, z);
+  }
+
+  private static class ReceiverData {
+    RobotConfig lastConfig;
+    boolean failedLastConversion = true;
+    int numberOfConfigs = 0;
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java
new file mode 100644
index 0000000000000000000000000000000000000000..2940aacbf26ae1a6e63cd7c763868d6f48639217
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read1Write2Test.java
@@ -0,0 +1,241 @@
+package org.jastadd.ragconnect.tests;
+
+import read1write2.ast.*;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "read-1-write-2".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class Read1Write2Test extends AbstractMqttTest {
+
+  private static final String TOPIC_SAME_READ = "same/read";
+  private static final String TOPIC_SAME_WRITE_INT = "same/write/int";
+  private static final String TOPIC_SAME_WRITE_STRING = "same/write/string";
+  private static final String TOPIC_DIFFERENT_READ = "different/read";
+  private static final String TOPIC_DIFFERENT_WRITE1_INT = "different/write1/int";
+  private static final String TOPIC_DIFFERENT_WRITE1_STRING = "different/write1/string";
+  private static final String TOPIC_DIFFERENT_WRITE2_INT = "different/write2/int";
+  private static final String TOPIC_DIFFERENT_WRITE2_STRING = "different/write2/string";
+  private static final String INITIAL_VALUE = "-1";
+
+  private MqttHandler handler;
+  private A model;
+  private OnSameNonterminal onSameNonterminal;
+  private OnDifferentNonterminal onDifferentNonterminal;
+  private TheOther other1;
+  private TheOther other2;
+
+  private ReceiverData dataSame;
+  private ReceiverData dataOther1;
+  private ReceiverData dataOther2;
+
+  @Override
+  public void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    // check initial value
+    TestUtils.waitForMqtt();
+    checkData(1, Integer.parseInt(INITIAL_VALUE), prefixed(INITIAL_VALUE), 1, Integer.parseInt(INITIAL_VALUE), prefixed(INITIAL_VALUE));
+
+    // set new value
+    sendData("2", "3");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(2, 2, prefixed("2"), 2, 3, prefixed("3"));
+
+    // set new value
+    sendData("4", "4");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(3, 4, prefixed("4"), 3, 4, prefixed("4"));
+
+    // set new value only for same
+    setDataOnlySame("77");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(4, 77, prefixed("77"), 3, 4, prefixed("4"));
+  }
+
+  private String prefixed(String s) {
+    return "prefix" + s;
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+// check initial value
+    TestUtils.waitForMqtt();
+    checkData(0, null, null, 0, null, null);
+
+    // set new value
+    sendData("2", "3");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(1, 2, prefixed("2"), 1, 3, prefixed("3"));
+
+    // set new value
+    sendData("4", "4");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(2, 4, prefixed("4"), 2, 4, prefixed("4"));
+
+    // set new value only for same
+    setDataOnlySame("77");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(3, 77, prefixed("77"), 2, 4, prefixed("4"));
+  }
+
+  @Override
+  protected void createModel() {
+    // Setting value for Input without dependencies does not trigger any updates
+    model = new A();
+
+    onSameNonterminal = new OnSameNonterminal();
+    model.setOnSameNonterminal(onSameNonterminal);
+    onSameNonterminal.setInput(INITIAL_VALUE);
+
+    onDifferentNonterminal = new OnDifferentNonterminal();
+    other1 = new TheOther();
+    other2 = new TheOther();
+    onDifferentNonterminal.addTheOther(other1);
+    onDifferentNonterminal.addTheOther(other2);
+    model.setOnDifferentNonterminal(onDifferentNonterminal);
+    onDifferentNonterminal.setInput(INITIAL_VALUE);
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    onSameNonterminal.addIntDependency(onSameNonterminal);
+    onSameNonterminal.addStringDependency(onSameNonterminal);
+    other1.addIntDependency(onDifferentNonterminal);
+    other1.addStringDependency(onDifferentNonterminal);
+    other2.addIntDependency(onDifferentNonterminal);
+    other2.addStringDependency(onDifferentNonterminal);
+
+    dataSame = new Read1Write2Test.ReceiverData();
+    dataOther1 = new Read1Write2Test.ReceiverData();
+    dataOther2 = new Read1Write2Test.ReceiverData();
+
+    handler.newConnection(TOPIC_SAME_WRITE_INT, bytes -> {
+      dataSame.numberOfIntValues += 1;
+      dataSame.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+    });
+    handler.newConnection(TOPIC_SAME_WRITE_STRING, bytes -> {
+      dataSame.numberOfStringValues += 1;
+      dataSame.lastStringValue = new String(bytes);
+    });
+
+    handler.newConnection(TOPIC_DIFFERENT_WRITE1_INT, bytes -> {
+      dataOther1.numberOfIntValues += 1;
+      dataOther1.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+    });
+    handler.newConnection(TOPIC_DIFFERENT_WRITE1_STRING, bytes -> {
+      dataOther1.numberOfStringValues += 1;
+      dataOther1.lastStringValue = new String(bytes);
+    });
+
+    handler.newConnection(TOPIC_DIFFERENT_WRITE2_INT, bytes -> {
+      dataOther2.numberOfIntValues += 1;
+      dataOther2.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+    });
+    handler.newConnection(TOPIC_DIFFERENT_WRITE2_STRING, bytes -> {
+      dataOther2.numberOfStringValues += 1;
+      dataOther2.lastStringValue = new String(bytes);
+    });
+
+    onSameNonterminal.connectInput(mqttUri(TOPIC_SAME_READ));
+    onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), writeCurrentValue);
+    onSameNonterminal.connectOutString(mqttUri(TOPIC_SAME_WRITE_STRING), writeCurrentValue);
+
+    onDifferentNonterminal.connectInput(mqttUri(TOPIC_DIFFERENT_READ));
+    other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), writeCurrentValue);
+    other1.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE1_STRING), writeCurrentValue);
+    other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), writeCurrentValue);
+    other2.connectOutString(mqttUri(TOPIC_DIFFERENT_WRITE2_STRING), writeCurrentValue);
+  }
+
+  private void sendData(String inputSame, String inputDifferent) {
+    handler.publish(TOPIC_SAME_READ, inputSame.getBytes());
+    handler.publish(TOPIC_DIFFERENT_READ, inputDifferent.getBytes());
+  }
+
+  private void setDataOnlySame(String inputSame) {
+    handler.publish(TOPIC_SAME_READ, inputSame.getBytes());
+  }
+
+  private void checkData(int numberOfSameValues, Integer lastSameIntValue, String lastSameStringValue,
+                         int numberOfDifferentValues, Integer lastDifferentIntValue,
+                         String lastDifferentStringValue) {
+    /* the value "-2" is never used in the test, so a test will always fail comparing to this value
+     especially, it is not the initial value */
+    ReceiverData expectedDataSame = ReceiverData.of(
+        numberOfSameValues,
+        lastSameIntValue != null ? lastSameIntValue : -2,
+        lastSameStringValue);
+    compareData(expectedDataSame, dataSame);
+    ReceiverData expectedDataDifferent = ReceiverData.of(
+        numberOfDifferentValues,
+        lastDifferentIntValue != null ? lastDifferentIntValue : -2,
+        lastDifferentStringValue);
+    compareData(expectedDataDifferent, dataOther1);
+    compareData(expectedDataDifferent, dataOther2);
+  }
+
+  private void compareData(ReceiverData expectedData,
+                         ReceiverData actual) {
+    assertEquals(expectedData.numberOfIntValues, actual.numberOfIntValues);
+    assertEquals(expectedData.numberOfStringValues, actual.numberOfStringValues);
+    if (expectedData.numberOfIntValues > 0) {
+      assertEquals(expectedData.lastIntValue, actual.lastIntValue);
+    }
+    if (expectedData.numberOfStringValues > 0) {
+      assertEquals(expectedData.lastStringValue, actual.lastStringValue);
+    }
+  }
+
+  private static class ReceiverData {
+    int lastIntValue;
+    int numberOfIntValues = 0;
+    String lastStringValue;
+    int numberOfStringValues = 0;
+
+    static ReceiverData of(int numberOfValues, int lastIntValue, String lastStringValue) {
+      ReceiverData result = new ReceiverData();
+      result.lastIntValue = lastIntValue;
+      result.lastStringValue = lastStringValue;
+      result.numberOfIntValues = numberOfValues;
+      result.numberOfStringValues = numberOfValues;
+      return result;
+    }
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java
new file mode 100644
index 0000000000000000000000000000000000000000..4790b173bab7cab2d6022e35c40125af7b79ed5f
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/Read2Write1Test.java
@@ -0,0 +1,232 @@
+package org.jastadd.ragconnect.tests;
+
+import read2write1.ast.*;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "read-1-write-2".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class Read2Write1Test extends AbstractMqttTest {
+
+  private static final String TOPIC_SAME_READ1 = "same/read1";
+  private static final String TOPIC_SAME_READ2 = "same/read2";
+  private static final String TOPIC_SAME_WRITE_INT = "same/write/int";
+  private static final String TOPIC_DIFFERENT_READ1 = "different/read1";
+  private static final String TOPIC_DIFFERENT_READ2 = "different/read2";
+  private static final String TOPIC_DIFFERENT_WRITE1_INT = "different/write1/int";
+  private static final String TOPIC_DIFFERENT_WRITE2_INT = "different/write2/int";
+  private static final String INITIAL_VALUE = "0";
+
+  private MqttHandler handler;
+  private A model;
+  private OnSameNonterminal onSameNonterminal;
+  private OnDifferentNonterminal onDifferentNonterminal;
+  private TheOther other1;
+  private TheOther other2;
+
+  private ReceiverData dataSame;
+  private ReceiverData dataOther1;
+  private ReceiverData dataOther2;
+
+  @Override
+  public void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    // check initial value
+    TestUtils.waitForMqtt();
+    checkData(1, Integer.parseInt(INITIAL_VALUE + INITIAL_VALUE),
+        1, Integer.parseInt(INITIAL_VALUE + INITIAL_VALUE));
+
+    // set new value
+    sendData(true, "2", true, "3");
+
+    // check new value. same: 2, 0. different: 3, 0.
+    TestUtils.waitForMqtt();
+    checkData(2, 20,
+        2, 30);
+
+    // set new value
+    sendData(false, "4", false, "4");
+
+    // check new value. same: 2, 4. different: 3, 4.
+    TestUtils.waitForMqtt();
+    checkData(3, 24,
+        3, 34);
+
+    // set new value only for same
+    setDataOnlySame(true, "77");
+
+    // check new value. same: 77, 4. different: 3, 4.
+    TestUtils.waitForMqtt();
+    checkData(4, 774,
+        3, 34);
+  }
+
+  private String prefixed(String s) {
+    return "prefix" + s;
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    // check initial value
+    TestUtils.waitForMqtt();
+    checkData(0, null,
+        0, null);
+
+    // set new value
+    sendData(true, "2", true, "3");
+
+    // check new value. same: 2, 0. different: 3, 0.
+    TestUtils.waitForMqtt();
+    checkData(1, 20,
+        1, 30);
+
+    // set new value
+    sendData(false, "4", false, "4");
+
+    // check new value. same: 2, 4. different: 3, 4.
+    TestUtils.waitForMqtt();
+    checkData(2, 24,
+        2, 34);
+
+    // set new value only for same
+    setDataOnlySame(true, "77");
+
+    // check new value. same: 77, 4. different: 3, 4.
+    TestUtils.waitForMqtt();
+    checkData(3, 774,
+        2, 34);
+  }
+
+  @Override
+  protected void createModel() {
+    // Setting value for Input without dependencies does not trigger any updates
+    model = new A();
+
+    onSameNonterminal = new OnSameNonterminal();
+    model.setOnSameNonterminal(onSameNonterminal);
+    onSameNonterminal.setInput1(INITIAL_VALUE);
+    onSameNonterminal.setInput2(INITIAL_VALUE);
+
+    onDifferentNonterminal = new OnDifferentNonterminal();
+    other1 = new TheOther();
+    other2 = new TheOther();
+    onDifferentNonterminal.addTheOther(other1);
+    onDifferentNonterminal.addTheOther(other2);
+    model.setOnDifferentNonterminal(onDifferentNonterminal);
+    onDifferentNonterminal.setInput1(INITIAL_VALUE);
+    onDifferentNonterminal.setInput2(INITIAL_VALUE);
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    onSameNonterminal.addInt1Dependency(onSameNonterminal);
+    onSameNonterminal.addInt2Dependency(onSameNonterminal);
+    other1.addInt1Dependency(onDifferentNonterminal);
+    other1.addInt2Dependency(onDifferentNonterminal);
+    other2.addInt1Dependency(onDifferentNonterminal);
+    other2.addInt2Dependency(onDifferentNonterminal);
+
+    dataSame = new Read2Write1Test.ReceiverData();
+    dataOther1 = new Read2Write1Test.ReceiverData();
+    dataOther2 = new Read2Write1Test.ReceiverData();
+
+    handler.newConnection(TOPIC_SAME_WRITE_INT, bytes -> {
+      dataSame.numberOfIntValues += 1;
+      dataSame.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+    });
+
+    handler.newConnection(TOPIC_DIFFERENT_WRITE1_INT, bytes -> {
+      dataOther1.numberOfIntValues += 1;
+      dataOther1.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+    });
+
+    handler.newConnection(TOPIC_DIFFERENT_WRITE2_INT, bytes -> {
+      dataOther2.numberOfIntValues += 1;
+      dataOther2.lastIntValue = java.nio.ByteBuffer.wrap(bytes).getInt();
+    });
+
+    onSameNonterminal.connectInput1(mqttUri(TOPIC_SAME_READ1));
+    onSameNonterminal.connectInput2(mqttUri(TOPIC_SAME_READ2));
+    onSameNonterminal.connectOutInteger(mqttUri(TOPIC_SAME_WRITE_INT), writeCurrentValue);
+
+    onDifferentNonterminal.connectInput1(mqttUri(TOPIC_DIFFERENT_READ1));
+    onDifferentNonterminal.connectInput2(mqttUri(TOPIC_DIFFERENT_READ2));
+    other1.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE1_INT), writeCurrentValue);
+    other2.connectOutInteger(mqttUri(TOPIC_DIFFERENT_WRITE2_INT), writeCurrentValue);
+  }
+
+  private void sendData(boolean useSameInput1, String inputSame,
+                        boolean useDifferentInput1, String inputDifferent) {
+    handler.publish(useSameInput1 ? TOPIC_SAME_READ1 : TOPIC_SAME_READ2,
+        inputSame.getBytes());
+    handler.publish(useDifferentInput1 ? TOPIC_DIFFERENT_READ1 : TOPIC_DIFFERENT_READ2,
+        inputDifferent.getBytes());
+  }
+
+  private void setDataOnlySame(boolean useSameInput1, String inputSame) {
+    handler.publish(useSameInput1 ? TOPIC_SAME_READ1 : TOPIC_DIFFERENT_READ2,
+        inputSame.getBytes());
+  }
+
+  private void checkData(int numberOfSameValues, Integer lastSameIntValue,
+                         int numberOfDifferentValues, Integer lastDifferentIntValue) {
+    /* the value "-2" is never used in the test, so a test will always fail comparing to this value
+     especially, it is not the initial value */
+    ReceiverData expectedDataSame = ReceiverData.of(
+        numberOfSameValues,
+        lastSameIntValue != null ? lastSameIntValue : -2
+    );
+    compareData(expectedDataSame, dataSame);
+    ReceiverData expectedDataDifferent = ReceiverData.of(
+        numberOfDifferentValues,
+        lastDifferentIntValue != null ? lastDifferentIntValue : -2
+    );
+    compareData(expectedDataDifferent, dataOther1);
+    compareData(expectedDataDifferent, dataOther2);
+  }
+
+  private void compareData(ReceiverData expectedData,
+                         ReceiverData actual) {
+    assertEquals(expectedData.numberOfIntValues, actual.numberOfIntValues);
+    if (expectedData.numberOfIntValues > 0) {
+      assertEquals(expectedData.lastIntValue, actual.lastIntValue);
+    }
+  }
+
+  private static class ReceiverData {
+    int lastIntValue;
+    int numberOfIntValues = 0;
+
+    static ReceiverData of(int numberOfValues, int lastIntValue) {
+      ReceiverData result = new ReceiverData();
+      result.lastIntValue = lastIntValue;
+      result.numberOfIntValues = numberOfValues;
+      return result;
+    }
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..143c74e984af40f9acf883ed7be2f3eb8adb253a
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java
@@ -0,0 +1,73 @@
+package org.jastadd.ragconnect.tests;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility methods for tests.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class TestUtils {
+
+  public static final double DELTA = 0.001d;
+
+  public static String getMqttHost() {
+    if (System.getenv("GITLAB_CI") != null) {
+      // we are in the CI, so use "mqtt" as host
+      return "mqtt";
+    } {
+      // else assume a locally running mqtt broker
+      return "localhost";
+    }
+  }
+
+  public static String mqttUri(String path) {
+    return "mqtt://" + getMqttHost() + "/" + path;
+  }
+
+  public static String restUri(String path, int port) {
+    return "rest://localhost:" + port + "/" + path;
+  }
+
+  public static int getMqttDefaultPort() {
+    return 1883;
+  }
+
+  public static int exec(Class<?> klass, String[] args, File err) throws IOException,
+      InterruptedException {
+    String javaHome = System.getProperty("java.home");
+    String javaBin = javaHome + File.separator + "bin" + File.separator + "java";
+    String classpath = System.getProperty("java.class.path");
+    String className = klass.getName();
+
+    String[] newArgs = new String[args.length + 4];
+    newArgs[0] = javaBin;
+    newArgs[1] = "-cp";
+    newArgs[2] = classpath;
+    newArgs[3] = className;
+    System.arraycopy(args, 0, newArgs, 4, args.length);
+
+    ProcessBuilder builder = new ProcessBuilder(newArgs);
+//    builder.redirectOutput(err);
+    builder.redirectError(err);
+
+    Process process = builder.start();
+    process.waitFor();
+    return process.exitValue();
+  }
+
+  public static String readFile(String path, Charset encoding)
+      throws IOException {
+    byte[] encoded = Files.readAllBytes(Paths.get(path));
+    return new String(encoded, encoding);
+  }
+
+  static void waitForMqtt() throws InterruptedException {
+    TimeUnit.SECONDS.sleep(2);
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc68f5bdb8b19167deb50fa54b52e933235bceb3
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TokenValueSendTest.java
@@ -0,0 +1,260 @@
+package org.jastadd.ragconnect.tests;
+
+import tokenValueSend.ast.*;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "tokenValueSend".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class TokenValueSendTest extends AbstractMqttTest {
+
+  private static final String TOPIC_SEND_ONE = "one/value/out";
+
+  private static final String TOPIC_RECEIVE_TWO = "two/value/in";
+  private static final String TOPIC_SEND_TWO = "two/value/out";
+
+  private static final String TOPIC_RECEIVE_THREE_VALUE = "three/value/in";
+  private static final String TOPIC_SEND_THREE_VALUE = "three/value/out";
+  private static final String TOPIC_SEND_THREE_OTHER = "three/other/out";
+
+  private static final String INITIAL_VALUE = "Start";
+
+  private MqttHandler handler;
+  private A model;
+  private OnlySend one;
+  private ReceiveAndSend two;
+  private ReceiveSendAndDepend three;
+
+  private ReceiverData dataOne;
+  private ReceiverData dataTwo;
+  private ReceiverData dataThree;
+  private ReceiverData dataThreeOther;
+
+  @Override
+  public void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    // check initial value
+    TestUtils.waitForMqtt();
+    checkData(1, "Start-Post",
+        1, "Start-Post",
+        1, "Start-Post",
+        1, "Start-T-Post");
+
+    // send new value
+    sendData("200", "300");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(1, "Start-Post",
+        2, "Pre-200-Post",
+        2, "Pre-300-Post",
+        2, "Pre-300-T-Post");
+
+    // set new value
+    setData("101", "201", "301");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(2, "101-Post",
+        3, "201-Post",
+        3, "301-Post",
+        3, "301-T-Post");
+
+    // send the same values (will not be sent again)
+    setData("101", "201", "301");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(2, "101-Post",
+        3, "201-Post",
+        3, "301-Post",
+        3, "301-T-Post");
+
+    // send values with prefixes imitating receiving
+    setData("102", "Pre-202", "Pre-302");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(3, "102-Post",
+        4, "Pre-202-Post",
+        4, "Pre-302-Post",
+        4, "Pre-302-T-Post");
+
+    // send the same values (will not be sent again, because previously prefixed)
+    sendData("202", "302");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(3, "102-Post",
+        4, "Pre-202-Post",
+        4, "Pre-302-Post",
+        4, "Pre-302-T-Post");
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+    // check initial value
+    TestUtils.waitForMqtt();
+    checkData(0, null,
+        0, null,
+        0, null,
+        0, null);
+
+    // send new value
+    sendData("210", "310");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(0, null,
+        1, "Pre-210-Post",
+        1, "Pre-310-Post",
+        1, "Pre-310-T-Post");
+
+    // set new value
+    setData("111", "211", "311");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(1, "111-Post",
+        2, "211-Post",
+        2, "311-Post",
+        2, "311-T-Post");
+
+    // send the same values (will not be sent again)
+    setData("111", "211", "311");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(1, "111-Post",
+        2, "211-Post",
+        2, "311-Post",
+        2, "311-T-Post");
+
+    // send values with prefixes imitating receiving
+    setData("112", "Pre-212", "Pre-312");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(2, "112-Post",
+        3, "Pre-212-Post",
+        3, "Pre-312-Post",
+        3, "Pre-312-T-Post");
+
+    // send the same values (will not be sent again, because previously prefixed)
+    sendData("212", "312");
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(2, "112-Post",
+        3, "Pre-212-Post",
+        3, "Pre-312-Post",
+        3, "Pre-312-T-Post");
+  }
+
+  @Override
+  protected void createModel() {
+    // Setting value for Input without dependencies does not trigger any updates
+    model = new A();
+
+    one = new OnlySend();
+    one.setValue(INITIAL_VALUE);
+    model.setOnlySend(one);
+
+    two = new ReceiveAndSend();
+    two.setValue(INITIAL_VALUE);
+    model.setReceiveAndSend(two);
+
+    three = new ReceiveSendAndDepend();
+    three.setValue(INITIAL_VALUE);
+    model.setReceiveSendAndDepend(three);
+  }
+
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    three.addDependency1(three);
+
+    dataOne = new ReceiverData();
+    dataTwo = new ReceiverData();
+    dataThree = new ReceiverData();
+    dataThreeOther = new ReceiverData();
+
+    handler.newConnection(TOPIC_SEND_ONE, bytes -> {
+      dataOne.numberOfStringValues += 1;
+      dataOne.lastStringValue = new String(bytes);
+    });
+    handler.newConnection(TOPIC_SEND_TWO, bytes -> {
+      dataTwo.numberOfStringValues += 1;
+      dataTwo.lastStringValue = new String(bytes);
+    });
+
+    handler.newConnection(TOPIC_SEND_THREE_VALUE, bytes -> {
+      dataThree.numberOfStringValues += 1;
+      dataThree.lastStringValue = new String(bytes);
+    });
+    handler.newConnection(TOPIC_SEND_THREE_OTHER, bytes -> {
+      dataThreeOther.numberOfStringValues += 1;
+      dataThreeOther.lastStringValue = new String(bytes);
+    });
+
+    one.connectValue(mqttUri(TOPIC_SEND_ONE), writeCurrentValue);
+    two.connectValue(mqttUri(TOPIC_RECEIVE_TWO));
+    two.connectValue(mqttUri(TOPIC_SEND_TWO), writeCurrentValue);
+    three.connectValue(mqttUri(TOPIC_RECEIVE_THREE_VALUE));
+    three.connectValue(mqttUri(TOPIC_SEND_THREE_VALUE), writeCurrentValue);
+    three.connectOtherOutput(mqttUri(TOPIC_SEND_THREE_OTHER), writeCurrentValue);
+  }
+
+  private void sendData(String inputTwo, String inputThree) {
+    handler.publish(TOPIC_RECEIVE_TWO, inputTwo.getBytes());
+    handler.publish(TOPIC_RECEIVE_THREE_VALUE, inputThree.getBytes());
+  }
+
+  private void setData(String inputOne, String inputTwo, String inputThree) {
+    one.setValue(inputOne);
+    two.setValue(inputTwo);
+    three.setValue(inputThree);
+  }
+
+  private void checkData(int numberOfOneValues, String lastOneStringValue,
+                         int numberOfTwoValues, String lastTwoStringValue,
+                         int numberOfThreeValues, String lastThreeStringValue,
+                         int numberOfOtherValues, String lastOtherStringValue
+  ) {
+    dataOne.assertEqualData(numberOfOneValues, lastOneStringValue);
+    dataTwo.assertEqualData(numberOfTwoValues, lastTwoStringValue);
+    dataThree.assertEqualData(numberOfThreeValues, lastThreeStringValue);
+    dataThreeOther.assertEqualData(numberOfOtherValues, lastOtherStringValue);
+  }
+
+  private static class ReceiverData {
+    String lastStringValue;
+    int numberOfStringValues = 0;
+
+    public void assertEqualData(int expectedNumberOfValues, String expectedLastValue) {
+      assertEquals(expectedNumberOfValues, this.numberOfStringValues);
+      assertEquals(expectedLastValue, this.lastStringValue);
+    }
+  }
+
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TutorialTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TutorialTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d7f346252639630d5818af4ceeef314b7ccecd9e
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TutorialTest.java
@@ -0,0 +1,68 @@
+package org.jastadd.ragconnect.tests;
+
+import tutorial.ast.A;
+import tutorial.ast.B;
+import tutorial.ast.MqttHandler;
+
+import java.io.IOException;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+
+/**
+ * Testcase "Tutorial".
+ *
+ * @author rschoene - Initial contribution
+ */
+public class TutorialTest extends AbstractMqttTest {
+
+  private MqttHandler handler;
+  private A a;
+  private B b1;
+  private B b2;
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (a != null) {
+      a.ragconnectCloseConnections();
+    }
+  }
+
+  @Override
+  protected void createModel() {
+    a = new A();
+    // set some default value for input
+    a.setInput("");
+    b1 = new B();
+    b2 = new B();
+    a.addB(b1);
+    a.addB(b2);
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    // a.OutputOnA -> a.Input
+    a.addDependencyA(a);
+    // b1.OutputOnB -> a.Input
+    b1.addDependencyB(a);
+    // b2.OutputOnB -> a.Input
+    b2.addDependencyB(a);
+
+    a.connectInput(mqttUri("topic/for/input"));
+    a.connectOutputOnA(mqttUri("a/out"), true);
+    b1.connectOutputOnB(mqttUri("b1/out"), true);
+    b2.connectOutputOnB(mqttUri("b2/out"), false);
+  }
+
+  @Override
+  protected void communicateSendInitialValue() {
+    // empty
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() {
+    // empty
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3c173522680500703be35e96289f0a6e35117b3a
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ViaTest.java
@@ -0,0 +1,324 @@
+package org.jastadd.ragconnect.tests;
+
+import org.junit.jupiter.api.Tag;
+import via.ast.A;
+import via.ast.MqttHandler;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import static org.jastadd.ragconnect.tests.TestUtils.mqttUri;
+import static org.jastadd.ragconnect.tests.TestUtils.restUri;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case "via".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("rest")
+public class ViaTest extends AbstractMqttTest {
+
+  private static final int REST_PORT = 9002;
+
+  private static final String TOPIC_MQTT_2_MQTT_RECEIVE = "mqtt2mqtt/receive";
+  private static final String PATH_REST_2_REST_RECEIVE = "rest2rest/receive";
+  private static final String TOPIC_MQTT_2_REST_RECEIVE = "mqtt2rest/receive";
+  private static final String PATH_REST_2_MQTT_RECEIVE = "rest2mqtt/receive";
+  private static final String TOPIC_BOTH_MQTT_RECEIVE = "both/send";
+  private static final String PATH_BOTH_REST_RECEIVE = "both/send";
+
+  private static final String TOPIC_MQTT_2_MQTT_SEND = "mqtt2mqtt/send";
+  private static final String PATH_REST_2_REST_SEND = "rest2rest/send";
+  private static final String PATH_MQTT_2_REST_SEND = "mqtt2rest/send";
+  private static final String TOPIC_REST_2_MQTT_SEND = "rest2mqtt/send";
+  private static final String TOPIC_BOTH_2_MQTT_SEND = "both2mqtt/send";
+  private static final String PATH_BOTH_2_REST_SEND = "both2rest/send";
+
+  private static final String REST_SERVER_BASE_URL = "http://localhost:" + REST_PORT + "/";
+
+  private MqttHandler handler;
+  private A model;
+  private ReceiverData dataMqtt2Mqtt;
+  private ReceiverData dataRest2Mqtt;
+  private WebTarget dataRest2Rest;
+  private WebTarget dataMqtt2Rest;
+  private ReceiverData dataBoth2Mqtt;
+  private WebTarget dataBoth2Rest;
+
+  private WebTarget senderRest2Rest;
+  private WebTarget senderRest2Mqtt;
+  private WebTarget senderBoth2Rest;
+
+  @Override
+  public void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+
+
+
+  @Override
+  protected void communicateSendInitialValue() throws InterruptedException {
+    // check initial value
+    TestUtils.waitForMqtt();
+    checkData(1, "100-M2M-ToMqtt",
+        "200-R2R-ToRest",
+        "300-M2R-ToRest",
+        1, "400-R2M-ToMqtt",
+        1, "500-B2M-ToMqtt",
+        "500-B2R-ToRest");
+
+    sendData("101", "201", "301", "401");
+    sendDataForBoth("501", true);
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(2, "FromMqtt-101-M2M-ToMqtt",
+        "FromRest-201-R2R-ToRest",
+        "FromMqtt-301-M2R-ToRest",
+        2, "FromRest-401-R2M-ToMqtt",
+        2, "501-B2M-ToMqtt",
+        "501-B2R-ToRest");
+
+    // send value only for bothInput via REST
+    sendDataForBoth("502", false);
+
+    // check this value
+    TestUtils.waitForMqtt();
+    checkData(2, "FromMqtt-101-M2M-ToMqtt",
+        "FromRest-201-R2R-ToRest",
+        "FromMqtt-301-M2R-ToRest",
+        2, "FromRest-401-R2M-ToMqtt",
+        3, "502-B2M-ToMqtt",
+        "502-B2R-ToRest");
+
+    // send same value only for bothInput via MQTT
+    sendDataForBoth("502", true);
+
+    // check this value
+    TestUtils.waitForMqtt();
+    checkData(2, "FromMqtt-101-M2M-ToMqtt",
+        "FromRest-201-R2R-ToRest",
+        "FromMqtt-301-M2R-ToRest",
+        2, "FromRest-401-R2M-ToMqtt",
+        3, "502-B2M-ToMqtt",
+        "502-B2R-ToRest");
+
+    // send values for other things
+    sendData("102", "202", "302", "402");
+
+    // check this value
+    TestUtils.waitForMqtt();
+    checkData(3, "FromMqtt-102-M2M-ToMqtt",
+        "FromRest-202-R2R-ToRest",
+        "FromMqtt-302-M2R-ToRest",
+        3, "FromRest-402-R2M-ToMqtt",
+        3, "502-B2M-ToMqtt",
+        "502-B2R-ToRest");
+
+    // send same values again for other things
+    sendData("102", "202", "302", "402");
+
+    // check this value
+    TestUtils.waitForMqtt();
+    checkData(3, "FromMqtt-102-M2M-ToMqtt",
+        "FromRest-202-R2R-ToRest",
+        "FromMqtt-302-M2R-ToRest",
+        3, "FromRest-402-R2M-ToMqtt",
+        3, "502-B2M-ToMqtt",
+        "502-B2R-ToRest");
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws InterruptedException {
+// check initial value
+    TestUtils.waitForMqtt();
+    checkData(0, null,
+        "200-R2R-ToRest",
+        "300-M2R-ToRest",
+        0, null,
+        0, null,
+        "500-B2R-ToRest");
+
+    sendData("111", "211", "311", "411");
+    sendDataForBoth("511", true);
+
+    // check new value
+    TestUtils.waitForMqtt();
+    checkData(1, "FromMqtt-111-M2M-ToMqtt",
+        "FromRest-211-R2R-ToRest",
+        "FromMqtt-311-M2R-ToRest",
+        1, "FromRest-411-R2M-ToMqtt",
+        1, "511-B2M-ToMqtt",
+        "511-B2R-ToRest");
+
+    // send value only for bothInput via REST
+    sendDataForBoth("512", false);
+
+    // check this value
+    TestUtils.waitForMqtt();
+    checkData(1, "FromMqtt-111-M2M-ToMqtt",
+        "FromRest-211-R2R-ToRest",
+        "FromMqtt-311-M2R-ToRest",
+        1, "FromRest-411-R2M-ToMqtt",
+        2, "512-B2M-ToMqtt",
+        "512-B2R-ToRest");
+
+    // send same value only for bothInput via MQTT
+    sendDataForBoth("512", true);
+
+    // check this value
+    TestUtils.waitForMqtt();
+    checkData(1, "FromMqtt-111-M2M-ToMqtt",
+        "FromRest-211-R2R-ToRest",
+        "FromMqtt-311-M2R-ToRest",
+        1, "FromRest-411-R2M-ToMqtt",
+        2, "512-B2M-ToMqtt",
+        "512-B2R-ToRest");
+
+    // send values for other things
+    sendData("112", "212", "312", "412");
+
+    // check this value
+    TestUtils.waitForMqtt();
+    checkData(2, "FromMqtt-112-M2M-ToMqtt",
+        "FromRest-212-R2R-ToRest",
+        "FromMqtt-312-M2R-ToRest",
+        2, "FromRest-412-R2M-ToMqtt",
+        2, "512-B2M-ToMqtt",
+        "512-B2R-ToRest");
+
+    // send same values again for other things
+    sendData("112", "212", "312", "412");
+
+    // check this value
+    TestUtils.waitForMqtt();
+    checkData(2, "FromMqtt-112-M2M-ToMqtt",
+        "FromRest-212-R2R-ToRest",
+        "FromMqtt-312-M2R-ToRest",
+        2, "FromRest-412-R2M-ToMqtt",
+        2, "512-B2M-ToMqtt",
+        "512-B2R-ToRest");
+  }
+
+  private void sendData(String inputMqtt2Mqtt, String inputRest2Rest, String inputMqtt2Rest, String inputRest2Mqtt) {
+    handler.publish(TOPIC_MQTT_2_MQTT_RECEIVE, inputMqtt2Mqtt.getBytes());
+    senderRest2Rest.request().put(Entity.entity(inputRest2Rest, MediaType.TEXT_PLAIN_TYPE));
+    handler.publish(TOPIC_MQTT_2_REST_RECEIVE, inputMqtt2Rest.getBytes());
+    senderRest2Mqtt.request().put(Entity.entity(inputRest2Mqtt, MediaType.TEXT_PLAIN_TYPE));
+  }
+
+  private void sendDataForBoth(String input, boolean useMqtt) {
+    if (useMqtt) {
+      handler.publish(TOPIC_BOTH_MQTT_RECEIVE, input.getBytes());
+    } else {
+      senderBoth2Rest.request().put(Entity.entity(input, MediaType.TEXT_PLAIN_TYPE));
+    }
+  }
+
+  private void checkData(int numberOfMqtt2MqttValues, String mqtt2MqttValue, String rest2RestValue, String mqtt2RestValue, int numberOfRest2MqttValues, String rest2MqttValue, int numberOfBoth2MqttValues, String both2MqttValue, String both2RestValue) {
+    dataMqtt2Mqtt.assertEqualData(numberOfMqtt2MqttValues, mqtt2MqttValue);
+    dataRest2Mqtt.assertEqualData(numberOfRest2MqttValues, rest2MqttValue);
+    dataBoth2Mqtt.assertEqualData(numberOfBoth2MqttValues, both2MqttValue);
+    assertEquals(rest2RestValue, readRest2Rest());
+    assertEquals(mqtt2RestValue, readMqtt2Rest());
+    assertEquals(both2RestValue, readBoth2Rest());
+  }
+
+  private String readRest2Rest() {
+    return dataRest2Rest.request().get().readEntity(String.class);
+  }
+
+  private String readMqtt2Rest() {
+    return dataMqtt2Rest.request().get().readEntity(String.class);
+  }
+
+  private String readBoth2Rest() {
+    return dataBoth2Rest.request().get().readEntity(String.class);
+  }
+
+  @Override
+  protected void createModel() {
+    // Setting value for Input without dependencies does not trigger any updates
+    model = new A();
+    model.setMqtt2MqttInput("100");
+    model.setRest2RestInput("200");
+    model.setMqtt2RestInput("300");
+    model.setRest2MqttInput("400");
+    model.setBoth2BothInput("500");
+  }
+
+  @Override
+  protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException {
+    model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS);
+
+    handler = new MqttHandler().dontSendWelcomeMessage().setHost(TestUtils.getMqttHost());
+    assertTrue(handler.waitUntilReady(2, TimeUnit.SECONDS));
+
+    model.addDependencyMqtt2Mqtt(model);
+    model.addDependencyRest2Rest(model);
+    model.addDependencyMqtt2Rest(model);
+    model.addDependencyRest2Mqtt(model);
+    model.addDependencyBoth2Mqtt(model);
+    model.addDependencyBoth2Rest(model);
+
+    dataMqtt2Mqtt = new ReceiverData();
+    dataRest2Mqtt = new ReceiverData();
+    dataBoth2Mqtt = new ReceiverData();
+
+    handler.newConnection(TOPIC_MQTT_2_MQTT_SEND, bytes -> {
+      dataMqtt2Mqtt.numberOfStringValues += 1;
+      dataMqtt2Mqtt.lastStringValue = new String(bytes);
+    });
+    handler.newConnection(TOPIC_REST_2_MQTT_SEND, bytes -> {
+      dataRest2Mqtt.numberOfStringValues += 1;
+      dataRest2Mqtt.lastStringValue = new String(bytes);
+    });
+    handler.newConnection(TOPIC_BOTH_2_MQTT_SEND, bytes -> {
+      dataBoth2Mqtt.numberOfStringValues += 1;
+      dataBoth2Mqtt.lastStringValue = new String(bytes);
+    });
+
+    Client client = ClientBuilder.newClient();
+    dataRest2Rest = client.target(REST_SERVER_BASE_URL + PATH_REST_2_REST_SEND);
+    dataMqtt2Rest = client.target(REST_SERVER_BASE_URL + PATH_MQTT_2_REST_SEND);
+    dataBoth2Rest = client.target(REST_SERVER_BASE_URL + PATH_BOTH_2_REST_SEND);
+    senderRest2Rest = client.target(REST_SERVER_BASE_URL + PATH_REST_2_REST_RECEIVE);
+    senderRest2Mqtt = client.target(REST_SERVER_BASE_URL + PATH_REST_2_MQTT_RECEIVE);
+    senderBoth2Rest = client.target(REST_SERVER_BASE_URL + PATH_BOTH_REST_RECEIVE);
+
+    model.connectMqtt2MqttInput(mqttUri(TOPIC_MQTT_2_MQTT_RECEIVE));
+    model.connectMqtt2MqttOutput(mqttUri(TOPIC_MQTT_2_MQTT_SEND), writeCurrentValue);
+    model.connectMqtt2RestInput(mqttUri(TOPIC_MQTT_2_REST_RECEIVE));
+    model.connectMqtt2RestOutput(restUri(PATH_MQTT_2_REST_SEND, REST_PORT), writeCurrentValue);
+    model.connectRest2MqttInput(restUri(PATH_REST_2_MQTT_RECEIVE, REST_PORT));
+    model.connectRest2MqttOutput(mqttUri(TOPIC_REST_2_MQTT_SEND), writeCurrentValue);
+    model.connectRest2RestInput(restUri(PATH_REST_2_REST_RECEIVE, REST_PORT));
+    model.connectRest2RestOutput(restUri(PATH_REST_2_REST_SEND, REST_PORT), writeCurrentValue);
+    model.connectBoth2BothInput(mqttUri(TOPIC_BOTH_MQTT_RECEIVE));
+    model.connectBoth2BothInput(restUri(PATH_BOTH_REST_RECEIVE, REST_PORT));
+    model.connectBoth2MqttOutput(mqttUri(TOPIC_BOTH_2_MQTT_SEND), writeCurrentValue);
+    model.connectBoth2RestOutput(restUri(PATH_BOTH_2_REST_SEND, REST_PORT), writeCurrentValue);
+  }
+
+  private static class ReceiverData {
+    String lastStringValue;
+    int numberOfStringValues = 0;
+
+    public void assertEqualData(int expectedNumberOfValues, String expectedLastValue) {
+      assertEquals(expectedNumberOfValues, this.numberOfStringValues);
+      assertEquals(expectedLastValue, this.lastStringValue);
+    }
+  }
+}
diff --git a/ragconnect.tests/src/test/proto/config.proto b/ragconnect.tests/src/test/proto/config.proto
new file mode 100644
index 0000000000000000000000000000000000000000..38cb5d0a25b02c96dcbdf30f685212ea972feee6
--- /dev/null
+++ b/ragconnect.tests/src/test/proto/config.proto
@@ -0,0 +1,7 @@
+syntax = "proto3";
+
+package config;
+
+message RobotConfig {
+  double speed = 1;
+}
\ No newline at end of file
diff --git a/ragconnect.tests/src/test/proto/robot_state.proto b/ragconnect.tests/src/test/proto/robot_state.proto
new file mode 100644
index 0000000000000000000000000000000000000000..6630631f16e00493be3f50307c9092e56184e9c6
--- /dev/null
+++ b/ragconnect.tests/src/test/proto/robot_state.proto
@@ -0,0 +1,37 @@
+syntax = "proto3";
+
+package robot;
+
+message RobotState {
+
+  message Position {
+    double x = 1;
+    double y = 2;
+    double z = 3;
+  }
+
+  message Orientation {
+    double x = 1;
+    double y = 2;
+    double z = 3;
+    double w = 4;
+  }
+
+  message LinearTwist {
+    double x = 1;
+    double y = 2;
+    double z = 3;
+  }
+
+  message AngularTwist {
+    double x = 1;
+    double y = 2;
+    double z = 3;
+  }
+
+  string name = 1;
+  Position position = 2;
+  Orientation orientation = 3;
+  LinearTwist linear_twist = 4;
+  AngularTwist angular_twist = 5;
+}
\ No newline at end of file
diff --git a/ragconnect.tests/src/test/proto/trajectory.proto b/ragconnect.tests/src/test/proto/trajectory.proto
new file mode 100644
index 0000000000000000000000000000000000000000..4a7f375e1ae37ba386f45e1149dc38990d0c7a0e
--- /dev/null
+++ b/ragconnect.tests/src/test/proto/trajectory.proto
@@ -0,0 +1,33 @@
+syntax = "proto3";
+
+package plan;
+
+message Trajectory {
+
+  message Position {
+    double x = 1;
+    double y = 2;
+    double z = 3;
+  }
+
+  message Orientation {
+    double x = 1;
+    double y = 2;
+    double z = 3;
+    double w = 4;
+  }
+
+  enum PlanningMode {
+    FLUID = 0;
+    CARTESIAN = 1;
+  }
+
+  message Pose {
+    Position position = 1;
+    Orientation orientation = 2;
+    PlanningMode mode = 3;
+  }
+
+  repeated Pose pose = 1;
+  bool loop = 2;
+}
diff --git a/ragconnect.tests/src/test/resources/log4j2.xml b/ragconnect.tests/src/test/resources/log4j2.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4c0d4548c61b23abad6aabc6811e68cd8a928871
--- /dev/null
+++ b/ragconnect.tests/src/test/resources/log4j2.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="INFO">
+    <Appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
+        </Console>
+    </Appenders>
+    <Loggers>
+        <Root level="debug">
+            <AppenderRef ref="Console"/>
+        </Root>
+        <Logger name="org.eclipse.jetty" level="info" additivity="false">
+            <AppenderRef ref="Console"/>
+        </Logger>
+    </Loggers>
+</Configuration>
diff --git a/settings.gradle b/settings.gradle
index 63cc07e51bd795158585056c756f0dc269181ff9..e7769874a5f3186274ceecbcd445feeb656cf9a4 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -2,3 +2,4 @@ rootProject.name = 'ragconnect'
 
 include 'relast-preprocessor'
 include 'ragconnect.base'
+include 'ragconnect.tests'