ECE366 - Lesson 10
Testing, Stomp
Instructor: Professor Hong
## Make a basic spring boot project
- [https://start.spring.io/](https://start.spring.io/)
## Add Sample Class
```
package com.example.demo;
public class Calculator {
public int multiplyByTwo(int x) {
return 2*x;
}
}
```
## Add Sample Tests
```
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
}
@Test
void testMultiplyByTwo() {
Calculator c = new Calculator();
int result = c.multiplyByTwo(2);
assertEquals(4, result);
}
}
```
## Testing Resources
[https://medium.com/capital-one-tech/improve-java-code-with-unit-tests-and-jacoco-b342643736ed](https://medium.com/capital-one-tech/improve-java-code-with-unit-tests-and-jacoco-b342643736ed)
[https://www.jetbrains.com/help/idea/running-test-with-coverage.html#run-config-with-coverage](https://www.jetbrains.com/help/idea/running-test-with-coverage.html#run-config-with-coverage)
## Types of Testing
- Unit testing - smallest pieces possible
- System testing - bigger part, complete API
- Integration testing - connecting different systems
- Acceptance testing - end user testing
- Performance testing - speed of program
- Regression testing - testing everything again
- Security testing - spot weaknesses in security
- Load testing - large loads / lots of hits
- End-to-end testing - entire system
## TDD
- Test Driven Development
- Written before implementation
- Tests will fail first and then succeed after writing code
- Forces developer to think about requirements
- Not skipped due to time pressure
- Bugs caught early
- Still need system and integration tests
## Advantages of Unit Testing
- Validating smallest units of software
- Find bugs easily and early
- Save time and money in the long run
- Forcing developers to write better and cleaner code
## JUnit
- Unit testing framework for Java
- Part of xUnit series
- Enables automated unit testing
## JUnit and Maven
- Create a new Maven project
- Add Maven dependency
```
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
</dependencies>
```
## Your First Test
src/main/java/Code.java
```
public class Code {
public String sayHello() {
return "Hello world!";
}
}
```
## Your First Test
test/java/CodeTest.java
```
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class CodeTest {
@Test
public void testSayHello() {
Code code = new Code();
assertEquals("Hello world!", code.sayHello());
}
}
```
## Bank Account
src/java/BankAccount.java
```
public class BankAccount {
private double balance;
private double minimumBalance;
public BankAccount(double balance, double minimumBalance) {
this.balance = balance;
this.minimumBalance = minimumBalance;
}
public double getBalance() {
return balance;
}
public double getMinimumBalance() {
return minimumBalance;
}
public double withdraw(double amount) {
if(balance - amount > minimumBalance) {
balance -= amount;
return amount;
} else {
throw new RuntimeException();
}
}
public double deposit(double amount) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return balance += amount;
}
}
```
## Bank Account Tests
test/java/BankAccountTest.java
```
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("Test BankAccount class")
public class BankAccountTest {
@Test
@DisplayName("Withdraw 500 successfully")
public void testWithdraw() {
BankAccount bankAccount = new BankAccount(500, -1000);
bankAccount.withdraw(300);
assertEquals(200, bankAccount.getBalance());
}
@Test
@DisplayName("Deposit 500 successfully")
public void testDeposit() {
BankAccount bankAccount = new BankAccount(400, 0);
bankAccount.deposit(500);
assertEquals(900, bankAccount.getBalance(), "Unexpected value, expected 900");
}
}
```
- DisplayName gives the tests a better name/description
## Assertions
- Check the outcome of the test
- If the assertion fails, the test fails
- Assertions class in the org.junit.jupiter.api package
- Many methods and overrides
- Add isActive and holderName
## Additional Properties for BankAccount
src/java/BankAccount.java
```
private boolean isActive = true;
private String holderName;
public boolean isActive() {
return isActive;
}
public void setActive(boolean active) {
isActive = active;
}
public String getHolderName() {
return holderName;
}
public void setHolderName(String holderName) {
this.holderName = holderName;
}
```
## Additional BankAccount Tests
tests/java/BankAccountAssertionTest.java
```
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertTimeout;
public class BankAccountAssertionsTest {
@Test
@DisplayName("Withdraw will become negative")
public void testWithdrawNotStuckAtZero() {
BankAccount bankAccount = new BankAccount(500, -1000);
bankAccount.withdraw(800);
assertNotEquals(0, bankAccount.getBalance());
}
@Test
@DisplayName("Test activation account after creation")
public void testActive() {
BankAccount bankAccount = new BankAccount(500, 0);
assertTrue(bankAccount.isActive());
}
@Test
@DisplayName("Test set holder name")
public void testHolderNameSet() {
BankAccount bankAccount = new BankAccount(500, 0);
bankAccount.setHolderName("Chris");
assertNotNull(bankAccount.getHolderName());
}
@Test
@DisplayName("Test that we can't withdraw below the minimum")
public void testNoWithdrawBelowMinimum() {
BankAccount bankAccount = new BankAccount(500, -1000);
assertThrows(RuntimeException.class, () -> bankAccount.withdraw(2000));
}
@Test
@DisplayName("Test no exceptions for withdraw and deposit")
public void testWithdrawAndDepositWithoutException() {
BankAccount bankAccount = new BankAccount(500, -1000);
assertAll(() -> bankAccount.deposit(200), () -> bankAccount.withdraw(450));
}
@Test
@DisplayName("Test speed deposit")
public void testDepositTimeout() {
BankAccount bankAccount = new BankAccount(400, 0);
assertTimeout(Duration.ofSeconds(1), () -> bankAccount.deposit(200));
}
// assertEquals, delta, message
// fail(); // fails the test
}
```
## Assumptions
- Setting a condition for executing a test
- If the assumption is met, the test will be executed
- If the assumption is not met, the test won't be executed
- Assumptions are in the org.junit.jupiter.api package
## Assumptions Test
tests/java/BankAccountAssumptionsTest.java
```
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.*;
public class BankAccountAssumptionsTest {
@Test
@DisplayName("Test activation account after creation")
public void testActive() {
BankAccount bankAccount = new BankAccount(500, 0);
assumeTrue(bankAccount != null);
assumeFalse(bankAccount == null);
assumingThat(bankAccount == null, () -> assertTrue(bankAccount.isActive()));
// doesn't abort the test and continues
assertTrue(bankAccount.isActive());
}
}
```
## Test Order
- Without specifying, we cannot predict the order of the tests
- Usually, this isn't a problem as the tests should be independent
- If you do need to order your tests, you can leverage the Order annotation
## Ordered Execution Tests
tests/java/BankAccountOrderedExecutionTest.java
```
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BankAccountOrderedExecutionTest {
static BankAccount bankAccount = new BankAccount(0,0);
@Test
@Order(2)
public void testWithdraw() {
bankAccount.withdraw(300);
assertEquals(200, bankAccount.getBalance());
}
@Test
@Order(1)
public void testDeposit() {
bankAccount.deposit(500);
assertEquals(500, bankAccount.getBalance());
}
}
```
## Nested Tests
- Used to control the relationship between tests
- Useful when you have feature separation or when code is organized around a method or feature
## Nested Tests
tests/java/BankAccountNestedTest.java
```
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class BankAccountNestedTest {
@Test
@DisplayName("Withdraw 500 successfully")
public void testWithdraw() {
BankAccount bankAccount = new BankAccount(500, -1000);
bankAccount.withdraw(300);
assertEquals(200, bankAccount.getBalance());
}
@Test
@DisplayName("Deposit 400 successfully")
public void testDeposit() {
BankAccount bankAccount = new BankAccount(400, 0);
bankAccount.deposit(500);
assertEquals(900, bankAccount.getBalance());
}
@Nested
class WhenBalanceEqualsZero {
@Test
@DisplayName("Withdrawing below minimum balance: exception")
public void testWithdrawMinimumBalanceIs0() {
BankAccount bankAccount = new BankAccount(0, 0);
assertThrows(RuntimeException.class, () -> bankAccount.withdraw(500));
}
@Test
@DisplayName("Wtihdrawing below minimum balance: negative balance")
public void testWithdrawMinimumBalanceNegative1000() {
BankAccount bankAccount = new BankAccount(0, -1000);
bankAccount.withdraw(500);
assertEquals(-500, bankAccount.getBalance());
}
}
}
```
## Dependency Injection
- Less tightly coupled classes
- No need to manually create instances everytime
- No need to create new BankAccount(0,0) any more
## Parameter Resolver
tests/java/BankAccountParameterResolver.java
```
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
public class BankAccountParameterResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType() == BankAccount.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return new BankAccount(0, 0);
}
}
```
## Dependency Injection Test
tests/java/BankAccountDITest.java
```
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(BankAccountParameterResolver.class)
public class BankAccountDITest {
@Test
@DisplayName("Deposit 500 successfully")
public void testDeposit(BankAccount bankAccount) {
bankAccount.deposit(500);
assertEquals(500, bankAccount.getBalance());
}
}
```
## Repeated Tests
tests/java/BankAccountRepeatedTest.java
```
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(BankAccountParameterResolver.class)
public class BankAccountRepeatedTest {
@RepeatedTest(5)
@DisplayName("Deposit 400 successfully")
public void testDeposit(BankAccount bankAccount) {
bankAccount.deposit(500);
assertEquals(500, bankAccount.getBalance());
}
@RepeatedTest(5)
@DisplayName("Deposit 400 successfully")
public void testDepositRepetitionInfo(BankAccount bankAccount, RepetitionInfo repetitionInfo) {
bankAccount.deposit(500);
assertEquals(500, bankAccount.getBalance());
System.out.println("Nr: " + repetitionInfo.getCurrentRepetition());
}
}
```
Great for repeating tests multiple times.
## Parameterized Tests
tests/java/BankAccountParameterizedTest.java
```
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.time.DayOfWeek;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(BankAccountParameterResolver.class)
public class BankAccountParameterizedTest {
@ParameterizedTest
@ValueSource(ints = {100, 400, 800, 1000})
@DisplayName("Deposit successfully")
public void testDeposit(int amount, BankAccount bankAccount) {
bankAccount.deposit(amount);
assertEquals(amount, bankAccount.getBalance());
}
@ParameterizedTest
@EnumSource(value = DayOfWeek.class, names = {"TUESDAY", "THURSDAY"})
public void testDayOfWeek(DayOfWeek day) {
assertTrue(day.toString().startsWith("T"));
}
@ParameterizedTest
@CsvSource({"100, Mary", "200, Richard", "150, Ted"})
//@CsvFileSource(resources = "details.csv", delimiter = ',')
public void depositAndNameTest(double amount, String name, BankAccount bankAccount) {
bankAccount.deposit(amount);
bankAccount.setHolderName(name);
assertEquals(amount, bankAccount.getBalance());
assertEquals(name, bankAccount.getHolderName());
}
}
```
- Run test several times with different parameters
## Timeout Tests
tests/java/BankAccountTimeoutTest.java
```
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTimeout;
// @Timeout(value=500, unit=imeUnit.MILLISECONDS)
@ExtendWith(BankAccountParameterResolver.class)
public class BankAccountTimeoutTest {
@Test
@Timeout(value=500, unit= TimeUnit.MILLISECONDS)
public void testDepositTimeoutAssertion(BankAccount bankAccount) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
bankAccount.deposit(300);
assertEquals(300, bankAccount.getBalance());
}
@Test
public void testDepositTimeoutAnnotation(BankAccount bankAccount) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bankAccount.deposit(300);
assertTimeout(Duration.ofMillis(500), () -> {
Thread.sleep(10);
});
}
}
```
## Parallel Execution Setup
tests/resources/junit-platform.properties
```
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=dynamic
```
## Parallel Execution
tests/java/BankAccountParallelExecutionTest.java
```
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Execution(ExecutionMode.CONCURRENT)
@ExtendWith(BankAccountParameterResolver.class)
public class BankAccountParallelExecutionTest {
@Test
@DisplayName("Deposit 500 successfully")
public void testDeposit(BankAccount bankAccount) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
bankAccount.deposit(500);
assertEquals(500, bankAccount.getBalance());
}
@Test
@DisplayName("Deposit 500 successfully")
public void testDeposit2(BankAccount bankAccount) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bankAccount.deposit(500);
assertEquals(500, bankAccount.getBalance());
}
@Test
@DisplayName("Deposit 500 successfully")
public void testDeposit3(BankAccount bankAccount) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bankAccount.deposit(500);
assertEquals(500, bankAccount.getBalance());
}
}
```
## Before and After Test
tests/java/BankAccountBeforeAndAfterTest.java
```
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // can use this for BeforeAll/AfterAll w/o static
public class BankAccountBeforeAndAfterTest {
static BankAccount bankAccount;
@BeforeAll
//@BeforeEach // fn must not be static
public void prepTest() {
System.out.println("Hi");
bankAccount = new BankAccount(500, 0);
}
@Test
public void testWithdraw() {
bankAccount.withdraw(300);
assertEquals(200, bankAccount.getBalance());
}
@Test
public void testDeposit() {
bankAccount.deposit(500);
assertEquals(1000, bankAccount.getBalance());
}
@AfterAll
//@AfterEach // fn must not be static
public void endTest() {
System.out.println("Bye");
}
}
```
## Conditional Tests
tests/java/BankAccountConditionalTest.java
```
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.*;
public class BankAccountConditionalTest {
@Test
@EnabledOnOs({OS.MAC})
public void testMac() {
}
@Test
@EnabledOnOs({OS.WINDOWS})
public void testWindows() {
}
@Test
@EnabledOnJre({JRE.JAVA_16})
public void testJRE() {
}
@Test
@DisabledOnJre({JRE.JAVA_16})
public void testNoJRE16() {
}
}
```
Generally not good practice
## Disable a Test
tests/java/BankAccountDisabledTest.java
```
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(BankAccountParameterResolver.class)
public class BankAccountDisabledTest {
@Test
@Disabled("Temporarily disabled due to maintenance")
@DisplayName("Deposit 500 successfully")
public void testDeposit() {
BankAccount bankAccount = new BankAccount(400, 0);
bankAccount.deposit(500);
assertEquals(900, bankAccount.getBalance(), "Unexpected value, expected 900");
}
}
```
## Mockito
- A way to mock functions/systems
pom.xml
```
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>2.23.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
```
## Calculate Methods
src/main/java/CalculateMethods.java
```
public class CalculateMethods {
public double divide(int x, int y) {
return x / y;
}
}
```
## Mockito Test
test/java/CalculateMethodsMockitoTest.java
```
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(MockitoExtension.class)
public class CalculateMethodsMockitoTest {
@Mock
CalculateMethods calculateMethods;
@BeforeEach
public void setupMocks() {
Mockito.when(calculateMethods.divide(6,3)).thenReturn(2.0);
}
@Test
public void testDivide() {
assertEquals(2.0, calculateMethods.divide(6,3));
}
}
```
## Surefire Reports
mvn.pom
```
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
```
```
mvn surefire-report:report
```
## Jacoco
pom.xml
```
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.9</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
```
```
mvn jacoco:report
```
## Best Practices
- Keep it simple
- Test the actual unit of code
- Clear naming and stick to naming conventions
- Low cyclomatic complexity - the fewer codes in path, the better
- Shouldn't have implementation in code
- Deterministic tests - you should always have the same result
## STOMP
- Simple/Streaming Text Oriented Message Protocol
- Allows you to create an interactive web application
- The web is the STOMP client
- The service is the message broker
- Example from [https://spring.io/guides/gs/messaging-stomp-websocket/](https://spring.io/guides/gs/messaging-stomp-websocket/)
## Pre-initialized Spring Initializr
[Pre-initialized Initializr](https://start.spring.io/#!type=maven-project&groupId=com.example&artifactId=messaging-stomp-websocket&name=messaging-stomp-websocket&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.messaging-stomp-websocket&dependencies=websocket)
Additional pom.xml dependencies
```
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>sockjs-client</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.4</version>
</dependency>
```
## HelloMessage Class
HelloMessage.java
```
package com.example.messagingstompwebsocket;
public class HelloMessage {
private String name;
public HelloMessage() {
}
public HelloMessage(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
```
## Greeting Class
Greeting.java
```
package com.example.messagingstompwebsocket;
public class Greeting {
private String content;
public Greeting() {
}
public Greeting(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
```
## GreetingController Class
GreetingController.java
```
package com.example.messagingstompwebsocket;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;
@Controller
public class GreetingController {
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
}
```
## WebSocketConfig Class
WebSocketConfig.java
```
package com.example.messagingstompwebsocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
```
## Index.html
src/main/resources/static/index.html
```
<!DOCTYPE html>
<html>
<head>
<title>Hello WebSocket</title>
<link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="/main.css" rel="stylesheet">
<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>
<script src="/app.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
enabled. Please enable
Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
<div class="row">
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="connect">WebSocket connection:</label>
<button id="connect" class="btn btn-default" type="submit">Connect</button>
<button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
</button>
</div>
</form>
</div>
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="name">What is your name?</label>
<input type="text" id="name" class="form-control" placeholder="Your name here...">
</div>
<button id="send" class="btn btn-default" type="submit">Send</button>
</form>
</div>
</div>
<div class="row">
<div class="col-md-12">
<table id="conversation" class="table table-striped">
<thead>
<tr>
<th>Greetings</th>
</tr>
</thead>
<tbody id="greetings">
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
```
## app.js
src/main/resources/static/app.js
```
var stompClient = null;
function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#greetings").html("");
}
function connect() {
var socket = new SockJS('/gs-guide-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
});
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
}
function showGreeting(message) {
$("#greetings").append("" + message + " |
");
}
$(function () {
$("form").on('submit', function (e) {
e.preventDefault();
});
$( "#connect" ).click(function() { connect(); });
$( "#disconnect" ).click(function() { disconnect(); });
$( "#send" ).click(function() { sendName(); });
});
```