Web Client For Integration Testing: A Simple Guide

by Alex Johnson 51 views

As developers, we often find ourselves in situations where we need to verify the real-time behavior of our applications, especially when dealing with technologies like WebSockets. This article guides you through creating a simple web client for integration testing, focusing on a scenario where we need to connect two players and ensure that WebSocket events are broadcasting correctly in real-time. We'll be using vanilla JavaScript to keep things simple and avoid the complexities of modern frameworks.

User Story: The Need for a Simple Visual Interface

Imagine you're developing a real-time multiplayer game, like Battleship. You need to ensure that when one player makes a move, the other player sees the update immediately. This requires a robust integration testing strategy. As a developer, I need a simple visual interface to connect two players and verify that WebSocket events (moves, hits, misses) are broadcasting correctly in real-time. This user story highlights the core need for a tool that allows us to visually inspect and confirm the correct behavior of our WebSocket-based application.

Why a Simple Web Client?

Before diving into the implementation, let's consider why a simple web client is an ideal solution for this scenario:

  • Visual Feedback: A visual interface provides immediate feedback on the application's state. We can see the grids, the hits, and the misses, making it easy to identify issues.
  • Real-time Verification: By observing the real-time updates, we can ensure that WebSocket events are being broadcast and received correctly.
  • Simplicity: Using vanilla JavaScript keeps the client lightweight and avoids the overhead of complex frameworks, allowing us to focus on the core functionality.
  • Debugging: A simple log box displaying incoming JSON messages can be invaluable for debugging, allowing us to inspect the raw data being exchanged between the client and server.

Acceptance Criteria: Defining the Scope

To ensure our web client meets the needs of the user story, we define a set of acceptance criteria. These criteria outline the specific functionalities and features that the client must possess:

  • Static HTML/JS Page: Create a static HTML/JS page in src/main/resources/static. This ensures the client is easily accessible and deployable within our project.
  • SockJS and Stomp.js: Implement SockJS and Stomp.js (client libraries) to connect to the backend. These libraries simplify WebSocket communication and provide a higher-level abstraction for message handling.
  • Two 10x10 Grids: Render two 10x10 grids (My Board, Opponent Board). These grids will visually represent the game boards, allowing us to track hits and misses.
  • Visual Feedback: Provide visual feedback for "Hit" (Red) and "Miss" (White). This makes it easy to see the results of each move at a glance.
  • Simple Log Box: Include a simple log box showing incoming JSON messages (for debugging). This allows us to inspect the raw data being exchanged between the client and server, aiding in troubleshooting.

These acceptance criteria provide a clear roadmap for the development of our web client, ensuring that we focus on the essential features needed for integration testing.

Technical Constraint: Keeping it Simple

One of the key considerations in this project is to maintain simplicity. To achieve this, we impose a technical constraint: No React/Angular/Vue. Use vanilla JavaScript to avoid build complexity. This constraint helps us focus on the core functionality without getting bogged down in the intricacies of modern JavaScript frameworks.

Why Vanilla JavaScript?

  • Reduced Complexity: Vanilla JavaScript eliminates the need for build tools, complex configurations, and the learning curve associated with frameworks.
  • Faster Development: Without the overhead of a framework, we can focus on writing the core logic of the client, leading to faster development times.
  • Improved Understanding: Working with vanilla JavaScript provides a deeper understanding of the underlying web technologies, which can be beneficial in the long run.
  • Lightweight: A vanilla JavaScript client is typically more lightweight than a framework-based client, resulting in faster load times and better performance.

By adhering to this technical constraint, we ensure that our web client remains simple, maintainable, and easy to understand.

Implementation Steps: Building the Web Client

Now that we have defined our user story, acceptance criteria, and technical constraints, let's dive into the implementation steps for building our basic web client.

1. Setting up the HTML Structure

First, we need to create the basic HTML structure for our web client. This includes the two 10x10 grids, the log box, and the necessary script tags. We'll start by creating an index.html file in the src/main/resources/static directory.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Battleship Client</title>
    <style>
        /* Basic CSS for the grids and log box */
        .grid {
            display: inline-block;
            margin: 10px;
        }
        .grid-cell {
            width: 20px;
            height: 20px;
            border: 1px solid black;
            display: inline-block;
        }
        .hit {
            background-color: red;
        }
        .miss {
            background-color: white;
        }
        #log {
            width: 400px;
            height: 200px;
            border: 1px solid black;
            overflow-y: scroll;
        }
    </style>
</head>
<body>
    <h1>Battleship Client</h1>
    <div class="grid" id="myBoard"></div>
    <div class="grid" id="opponentBoard"></div>
    <div id="log"></div>
    <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/bundles/stomp.min.js"></script>
    <script src="script.js"></script>
</body>
</html>

This HTML structure sets up the basic layout for our web client. We have two div elements with the IDs myBoard and opponentBoard to represent the grids, and a div with the ID log for the log box. We also include the necessary CSS for styling the grids and the log box. Finally, we include the SockJS and Stomp.js libraries from CDNs and our custom script.js file.

2. Implementing the JavaScript Logic

Next, we need to implement the JavaScript logic to handle WebSocket connections, render the grids, and update them based on incoming messages. We'll create a script.js file in the same directory as our index.html file.

// script.js

document.addEventListener('DOMContentLoaded', () => {
    const myBoard = document.getElementById('myBoard');
    const opponentBoard = document.getElementById('opponentBoard');
    const log = document.getElementById('log');

    // Function to create a grid
    function createGrid(boardElement) {
        for (let i = 0; i < 10; i++) {
            for (let j = 0; j < 10; j++) {
                const cell = document.createElement('div');
                cell.classList.add('grid-cell');
                cell.dataset.x = j;
                cell.dataset.y = i;
                boardElement.appendChild(cell);
            }
        }
    }

    // Function to log messages
    function logMessage(message) {
        const logEntry = document.createElement('div');
        logEntry.textContent = message;
        log.appendChild(logEntry);
        log.scrollTop = log.scrollHeight; // Scroll to the bottom
    }

    // Create the grids
    createGrid(myBoard);
    createGrid(opponentBoard);

    // Connect to the WebSocket endpoint
    const socket = new SockJS('/ws');
    const stompClient = Stomp.over(socket);

    stompClient.connect({}, (frame) => {
        logMessage('Connected: ' + frame);
        stompClient.subscribe('/topic/game-events', (message) => {
            const gameEvent = JSON.parse(message.body);
            logMessage('Received: ' + message.body);
            updateBoard(gameEvent);
        });
    });

    // Function to update the board based on the game event
    function updateBoard(gameEvent) {
        const { target, x, y, result } = gameEvent;
        const board = target === 'opponent' ? myBoard : opponentBoard;
        const cell = board.querySelector(`.grid-cell[data-x="${x}"][data-y="${y}"]`);
        if (cell) {
            if (result === 'hit') {
                cell.classList.add('hit');
            } else if (result === 'miss') {
                cell.classList.add('miss');
            }
        }
    }

    // Add click listeners to the opponent board
    opponentBoard.addEventListener('click', (event) => {
        if (event.target.classList.contains('grid-cell')) {
            const x = event.target.dataset.x;
            const y = event.target.dataset.y;
            stompClient.send('/app/fire', {}, JSON.stringify({ x: parseInt(x), y: parseInt(y) }));
        }
    });
});

This JavaScript code does the following:

  • DOMContentLoaded Listener: Waits for the DOM to be fully loaded before executing the code.
  • Grid Creation: Creates the two 10x10 grids by dynamically generating div elements and appending them to the respective board elements.
  • Log Message Function: Implements the logMessage function to display messages in the log box, scrolling to the bottom to show the latest messages.
  • WebSocket Connection: Connects to the WebSocket endpoint using SockJS and Stomp.js, subscribing to the /topic/game-events topic to receive game events.
  • Update Board Function: Implements the updateBoard function to update the grid cells based on the received game events, adding the hit or miss class as appropriate.
  • Click Listeners: Adds click listeners to the opponent board, allowing the user to send fire events to the server.

3. Backend Integration (Conceptual)

To fully test our web client, we need a backend that supports WebSocket communication and broadcasts game events. While the specific implementation of the backend is beyond the scope of this article, here's a conceptual overview of what's required:

  • WebSocket Endpoint: The backend needs to expose a WebSocket endpoint (e.g., /ws) that our client can connect to.
  • Message Handling: The backend needs to handle incoming messages (e.g., fire events) and broadcast game events (e.g., hit, miss) to the connected clients.
  • Game Logic: The backend should implement the core game logic, such as determining hits and misses, and updating the game state.

For example, using Spring Boot, you might create a WebSocket configuration class and a controller to handle the game logic and message broadcasting.

Visual Feedback: Hits and Misses

One of the key acceptance criteria is to provide visual feedback for hits and misses. In our implementation, we achieve this by adding CSS classes to the grid cells based on the received game events. The hit class sets the background color to red, while the miss class sets it to white.

.hit {
    background-color: red;
}
.miss {
    background-color: white;
}

This visual feedback allows us to quickly and easily see the results of each move, making it easier to verify the correct behavior of our application.

Simple Log Box: Debugging Made Easy

The simple log box is an invaluable tool for debugging. By displaying incoming JSON messages, we can inspect the raw data being exchanged between the client and server. This can help us identify issues such as incorrect message formats, missing data, or unexpected events.

function logMessage(message) {
    const logEntry = document.createElement('div');
    logEntry.textContent = message;
    log.appendChild(logEntry);
    log.scrollTop = log.scrollHeight; // Scroll to the bottom
}

The logMessage function appends new messages to the log box and automatically scrolls to the bottom, ensuring that the latest messages are always visible.

Conclusion: A Simple Yet Powerful Tool

In conclusion, creating a basic web client for integration testing using vanilla JavaScript is a simple yet powerful approach. By adhering to the principles of simplicity and focusing on the core functionality, we can build a tool that effectively verifies the real-time behavior of our applications. This client, with its visual feedback and log box, allows us to ensure that WebSocket events are broadcasting correctly, ultimately leading to a more robust and reliable application.

For further information on web development best practices, consider visiting the Mozilla Developer Network.