Skip to content

Commit ab31487

Browse files
committed
update websocket example
1 parent f4f21fa commit ab31487

File tree

14 files changed

+318
-14
lines changed

14 files changed

+318
-14
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,5 @@ This repository also contains examples of how to use Karate and demonstrates int
5959
* [Slack](slack/README.md) - Refer to blog posts and sample code from the community
6060
* [Spring Boot](spring-boot/README.md) - Resources on testing Spring Boot applications with Karate
6161
* [SSH](ssh/README.md) - how to open an SSH connection and invoke server commands using Karate
62-
* [WebSockets](websockets/README.md) - Built-in support for WebSockets or you can use Java interop for very advanced scenarios
62+
* [WebSocket](websocket/README.md) - Built-in support for WebSocket or you can use Java interop for very advanced scenarios
6363
* [Xray](https://docs.getxray.app/display/XRAYCLOUD/Testing+APIs+using+Karate+DSL) - Official documentation from the Xray team on how to integrate Karate

kafka/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Karate Kafka
22

3-
Karate has native support for Kafka as an optional dependency (non-open source and commercial). Enterprise users can find more information here: [Karate-Kafka](https://github.com/karatelabs/karate-addons/tree/main/karate-kafka).
3+
Karate has native support for [Apache Kafka](https://kafka.apache.org) as an optional dependency (non-open source and commercial). Enterprise users can find more information here: [Karate-Kafka](https://github.com/karatelabs/karate-addons/tree/main/karate-kafka).
44

55
A license is required for running (for e.g. in CI/CD) and a Karate Labs IDE subscription is required per developer seat.
66

websocket/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Karate Websocket
2+
3+
Karate has native support for [the Websocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) as an optional dependency (non-open source and commercial). Enterprise users can find more information here: [Karate-Websocket](https://github.com/karatelabs/karate-addons/tree/main/karate-websocket).
4+
5+
A license is required for running (for e.g. in CI/CD) and a Karate Labs IDE subscription is required per developer seat.
6+
7+
This sample project shows how to handle the async nature of Websocket communication and handle a custom protocol such as [STOMP](https://stomp.github.io/). Make sure you have a `.karate/karate.lic` file in place before running the example.
8+
9+
## Running
10+
11+
This is a normal Java / Maven project. To run the simple [`echo.feature`](src/test/java/karate/echo.feature) you can do: `mvn clean test`.
12+
13+
There is also [`json.feature`](src/test/java/karate/json.feature) that can be run as follows: `mvn test -Dtest=JsonRunner`. This shows how the built-in `io.karatelabs.websocket.JsonAdapter` can be used if all messages are known to be JSON.
14+
15+
There is an advanced example: [`stomp.feature`](src/test/java/karate/stomp.feature) that shows how to fully control the flow of messages and the conversion of text (or bytes) to and from JSON in the test. Concepts such as how to implement a message adapter i.e. [`StompAdapter.java`](src/test/java/karate/StompAdapter.java) are explained in the [documentation](https://github.com/karatelabs/karate-addons/tree/main/karate-websocket).
16+
17+
To run the STOMP example, you need to download and start this Spring-Boot based project: [gs-messaging-stomp-websocket
18+
](https://github.com/spring-guides/gs-messaging-stomp-websocket). The [`/complete`](https://github.com/spring-guides/gs-messaging-stomp-websocket/tree/main/complete) directory has a ready to run Maven project which you can start from the command-line like this:
19+
20+
```
21+
mvn exec:java -Dexec.mainClass="com.example.messagingstompwebsocket.MessagingStompWebsocketApplication"
22+
```
23+
24+
You can also open the `complete` project within a Java IDE and run the main class referred in the command above as an alternative.
25+
26+
This will start a local web-app on port 8080 and you can also view a demo web-page at `http://localhost:8080`. Now you can run the Karate test that communicates to the just-started server via STOMP over Websocket like this:
27+
28+
```
29+
mvn test -Dtest=StompRunner
30+
```
31+
32+
## Further Reading
33+
34+
Karate has had very basic support for websockets in the open-source version which may suffice for some teams. The following information is available:
35+
36+
* [Documentation](https://github.com/karatelabs/karate#websocket)
37+
* Examples:
38+
* [echo.feature](https://github.com/karatelabs/karate/blob/master/karate-demo/src/test/java/demo/websocket/echo.feature)
39+
* [websocket.feature](https://github.com/karatelabs/karate/blob/master/karate-demo/src/test/java/demo/websocket/websocket.feature)
40+
* [Karate and WebSockets on Stack Overflow](https://stackoverflow.com/search?q=%5Bkarate%5D+websocket)
41+
* [Using a custom Java implementation if required](https://stackoverflow.com/a/69299321/143475)

websocket/pom.xml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<groupId>io.karatelabs.examples</groupId>
6+
<artifactId>karate-websocket-example</artifactId>
7+
<version>1.0-SNAPSHOT</version>
8+
<packaging>jar</packaging>
9+
10+
<properties>
11+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
12+
<java.version>17</java.version>
13+
<maven.compiler.version>3.11.0</maven.compiler.version>
14+
<maven.surefire.version>3.0.0</maven.surefire.version>
15+
<junit5.version>5.6.2</junit5.version>
16+
</properties>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>io.karatelabs</groupId>
21+
<artifactId>karate-websocket</artifactId>
22+
<version>0.1.0</version>
23+
<scope>test</scope>
24+
</dependency>
25+
<dependency>
26+
<groupId>org.junit.jupiter</groupId>
27+
<artifactId>junit-jupiter</artifactId>
28+
<version>${junit5.version}</version>
29+
<scope>test</scope>
30+
</dependency>
31+
</dependencies>
32+
33+
<build>
34+
<testResources>
35+
<testResource>
36+
<directory>src/test/java</directory>
37+
<excludes>
38+
<exclude>**/*.java</exclude>
39+
</excludes>
40+
</testResource>
41+
</testResources>
42+
<plugins>
43+
<plugin>
44+
<groupId>org.apache.maven.plugins</groupId>
45+
<artifactId>maven-compiler-plugin</artifactId>
46+
<version>${maven.compiler.version}</version>
47+
<configuration>
48+
<encoding>UTF-8</encoding>
49+
<source>${java.version}</source>
50+
<target>${java.version}</target>
51+
</configuration>
52+
</plugin>
53+
<plugin>
54+
<groupId>org.apache.maven.plugins</groupId>
55+
<artifactId>maven-surefire-plugin</artifactId>
56+
<version>${maven.surefire.version}</version>
57+
<configuration>
58+
<argLine>-Dfile.encoding=UTF-8</argLine>
59+
</configuration>
60+
</plugin>
61+
</plugins>
62+
</build>
63+
64+
</project>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function fn() {
2+
3+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package karate;
2+
3+
import com.intuit.karate.Results;
4+
import com.intuit.karate.Runner;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
9+
class EchoTest {
10+
11+
@Test
12+
void testFeature() {
13+
Results results = Runner.path("classpath:karate/echo.feature").parallel(1);
14+
assertEquals(0, results.getFailCount(), results.getErrorMessages());
15+
}
16+
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package karate;
2+
3+
import com.intuit.karate.Results;
4+
import com.intuit.karate.Runner;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
9+
class JsonRunner {
10+
11+
@Test
12+
void testFeature() {
13+
Results results = Runner.path("classpath:karate/json.feature").parallel(1);
14+
assertEquals(0, results.getFailCount(), results.getErrorMessages());
15+
}
16+
17+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package karate;
2+
3+
import com.intuit.karate.Json;
4+
import io.karatelabs.websocket.WebsocketAdapter;
5+
import io.karatelabs.websocket.WebsocketConsumer;
6+
7+
import java.util.LinkedHashMap;
8+
import java.util.Map;
9+
10+
public class StompAdapter implements WebsocketAdapter<Map<String, Object>, String> {
11+
12+
@Override
13+
public void onMessage(WebsocketConsumer client, Map<String, Object> msg) {
14+
client.receive(msg);
15+
}
16+
17+
@SuppressWarnings("unchecked")
18+
@Override
19+
public String toWire(Map<String, Object> map) {
20+
String command = (String) map.get("command");
21+
Map<String, Object> headers = (Map<String, Object>) map.get("headers");
22+
Object body = map.get("body");
23+
if (body instanceof Map) {
24+
body = Json.of(body).toString();
25+
}
26+
StringBuilder sb = new StringBuilder();
27+
sb.append(command).append('\n');
28+
if (headers != null) {
29+
headers.forEach((k, v) -> {
30+
sb.append(k).append(':').append(v).append('\n');
31+
});
32+
}
33+
sb.append('\n');
34+
if (body != null) {
35+
sb.append(body);
36+
}
37+
sb.append('\0');
38+
return sb.toString();
39+
}
40+
41+
@Override
42+
public Map<String, Object> fromWire(String text) {
43+
Map<String, Object> map = new LinkedHashMap<>();
44+
String[] lines = text.split("\\R");
45+
Map<String, String> headers = new LinkedHashMap<>();
46+
boolean headersDone = false;
47+
for (String line : lines) {
48+
if (map.isEmpty()) {
49+
map.put("command", line);
50+
} else if (line.isEmpty()) {
51+
map.put("headers", headers);
52+
headersDone = true;
53+
} else if ("\0".equals(line)) {
54+
continue;
55+
} else {
56+
if (headersDone) {
57+
line = line.trim();
58+
if (line.charAt(0) == '{') {
59+
map.put("body", Json.of(line).asMap());
60+
} else {
61+
map.put("body", line);
62+
}
63+
} else {
64+
int pos = line.indexOf(':');
65+
if (pos == -1) {
66+
throw new RuntimeException("unexpected header: " + line);
67+
}
68+
String key = line.substring(0, pos);
69+
String value = line.substring(pos + 1);
70+
headers.put(key, value);
71+
}
72+
}
73+
}
74+
return map;
75+
}
76+
77+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package karate;
2+
3+
import com.intuit.karate.Results;
4+
import com.intuit.karate.Runner;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
9+
class StompRunner {
10+
11+
@Test
12+
void testFeature() {
13+
Results results = Runner.path("classpath:karate/stomp.feature").parallel(1);
14+
assertEquals(0, results.getFailCount(), results.getErrorMessages());
15+
}
16+
17+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Feature: demo plain-text
2+
3+
Scenario:
4+
* def session = karate.channel('websocket')
5+
* session.url = 'wss://ws.postman-echo.com/raw'
6+
* session.start()
7+
8+
* session.send('hello')
9+
10+
* def response = session.collect()
11+
* match response == ['hello']
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Feature: demo auto-conversion to json
2+
3+
Scenario:
4+
* def session = karate.channel('websocket')
5+
* session.url = 'wss://ws.postman-echo.com/raw'
6+
* def Adapter = Java.type('io.karatelabs.websocket.JsonAdapter')
7+
* session.adapter = new Adapter()
8+
* session.start()
9+
10+
* session.send({ message: 'hello' })
11+
12+
* def response = session.collect()
13+
* match response == [{ message: 'hello' }]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Feature: tests the following spring-websocket example
2+
https://github.com/spring-guides/gs-messaging-stomp-websocket
3+
4+
Background:
5+
* def result =
6+
"""
7+
function(name) {
8+
var session = karate.get('session');
9+
var message = { command: 'SEND', headers: { destination: '/app/hello' }, body: { name: name } };
10+
session.send(message);
11+
var response = session.pop();
12+
return response.body.content;
13+
}
14+
"""
15+
16+
Scenario:
17+
* def session = karate.channel('websocket')
18+
* session.url = 'ws://localhost:8080/gs-guide-websocket'
19+
* def Adapter = Java.type('karate.StompAdapter')
20+
* session.adapter = new Adapter()
21+
* session.start()
22+
* session.send({ command: 'CONNECT', headers: { 'accept-version': '1.2', 'heart-beat': '0,0' } })
23+
* match session.pop() contains { command: 'CONNECTED' }
24+
* session.send({ command: 'SUBSCRIBE', headers: { id: 'sub-0', destination: '/topic/greetings' } })
25+
26+
* match result('foo') == 'Hello, foo!'
27+
* match result('bar') == 'Hello, bar!'
28+
29+
* session.send({ command: 'DISCONNECT', headers: { receipt: 'disc-0' } })
30+
* match session.pop() contains { command: 'RECEIPT', headers: { 'receipt-id': 'disc-0' } }
31+
* session.stop()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<configuration>
3+
4+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
5+
<encoder>
6+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
7+
</encoder>
8+
</appender>
9+
10+
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
11+
<file>target/karate.log</file>
12+
<encoder>
13+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
14+
</encoder>
15+
</appender>
16+
17+
<logger name="com.intuit" level="DEBUG"/>
18+
<logger name="io.karatelabs" level="DEBUG"/>
19+
20+
<root level="warn">
21+
<appender-ref ref="STDOUT" />
22+
<appender-ref ref="FILE" />
23+
</root>
24+
25+
</configuration>

websockets/README.md

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)