Skip to main content

WebSocket Mock Guide

This guide explains how to use the WebSocket mock for testing real-time communication in the Runway Calculator application.

Overview

The WebSocket mock allows you to:

  1. Intercept and mock WebSocket connections
  2. Simulate server messages and events
  3. Validate outgoing messages
  4. Test error scenarios and edge cases

Basic Usage

Installation and Setup

use yew_wasm_starter_runway_calculator::tests::api_integration::websocket_mock::WebSocketMock;

#[wasm_bindgen_test]
async fn test_websocket_functionality() {
// Create and install WebSocket mock
let ws_mock = WebSocketMock::new();
ws_mock.install().expect("Failed to install WebSocket mock");

// Add mock responses
ws_mock.add_response("wss://example.com", true);

// Your test code here...

// Clean up when done
ws_mock.uninstall().expect("Failed to uninstall WebSocket mock");
}

Configuring Mock Responses

// Basic auto-connect response
ws_mock.add_response("wss://example.com", true);

// More detailed configuration
ws_mock.add_response_with_config(
"wss://example.com", // URL pattern to match
vec!["Message 1", "Message 2"], // Automatic messages to send
true, // Auto-connect when client connects
Some(50), // Connection delay in milliseconds
true, // Close after sending all messages
Some(1000), // Close code
Some("Normal close") // Close reason
);

// Simulate connection errors
ws_mock.simulate_connection_error("wss://error.example.com");

Sending Manual Messages

// Send a message to all connected clients
ws_mock.send_message("Hello from server");

// Send a message to specific connections matching a URL pattern
ws_mock.send_message_to_matching("Hello specific clients", Some("example.com"));

Verifying Outgoing Messages

// Check all sent messages
let messages = ws_mock.get_sent_messages();
assert_eq!(messages.len(), 2);
assert_eq!(messages[0].data, "Hello server");

// Find messages containing specific content
let login_messages = ws_mock.get_messages_containing("login");
assert_eq!(login_messages.len(), 1);

Advanced Features

Message Validation

You can validate outgoing messages against a JSON schema:

// Add basic validation
let schema = r#"{
"type": "object",
"required": ["type", "data"],
"properties": {
"type": { "type": "string" },
"data": { "type": "object" }
}
}"#;

ws_mock.add_message_validation(schema).expect("Failed to add validation");

// Add validation with custom error response
ws_mock.add_message_validation_with_error(
schema,
"Invalid message format: message must contain type and data fields"
).expect("Failed to add validation");

Named Validations for Different Message Types

// Add validation for specific message types
let login_schema = r#"{
"type": "object",
"required": ["type", "username", "password"],
"properties": {
"type": { "type": "string" },
"username": { "type": "string" },
"password": { "type": "string" }
}
}"#;

ws_mock.add_named_message_validation("login", login_schema)
.expect("Failed to add login validation");

Controlling Connection State

// Close all active connections
ws_mock.close_all(Some(1000), Some("Server shutting down"));

// Close specific connections
ws_mock.close_matching("chat.example.com", Some(1001), Some("Chat service unavailable"));

// Trigger errors
ws_mock.trigger_error("Server internal error");
ws_mock.trigger_error_on_matching("api.example.com", "API service unavailable");

Common Testing Patterns

Testing Automatic Reconnection

#[wasm_bindgen_test]
async fn test_reconnection() {
let ws_mock = WebSocketMock::new();
ws_mock.install().expect("Failed to install WebSocket mock");

// Configure initial connection
ws_mock.add_response("wss://reconnect.example.com", true);

// Create the WebSocket connection
let ws = web_sys::WebSocket::new("wss://reconnect.example.com").unwrap();

// Wait for connection to open...

// Simulate a connection close from server
ws_mock.close_matching("reconnect.example.com", Some(1001), Some("Temporary disconnection"));

// Wait for reconnection attempt...

// Verify that a new connection was attempted
let connections = ws_mock.get_connections();
assert!(connections.len() > 1, "Should attempt to reconnect");

ws_mock.uninstall().expect("Failed to uninstall WebSocket mock");
}

Testing Message Handling

#[wasm_bindgen_test]
async fn test_message_handling() {
let ws_mock = WebSocketMock::new();
ws_mock.install().expect("Failed to install WebSocket mock");

// Configure connection
ws_mock.add_response("wss://message.example.com", true);

// Setup message handling
let messages = Rc::new(RefCell::new(Vec::new()));
let messages_clone = messages.clone();

let ws = web_sys::WebSocket::new("wss://message.example.com").unwrap();

let onmessage = Closure::wrap(Box::new(move |e: MessageEvent| {
let data = e.data().as_string().unwrap();
messages_clone.borrow_mut().push(data);
}) as Box<dyn FnMut(MessageEvent)>);

ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
onmessage.forget();

// Wait for connection to open...

// Send test messages
ws_mock.send_message("Message 1");
ws_mock.send_message("Message 2");

// Wait for processing...

// Verify handling
assert_eq!(messages.borrow().len(), 2);
assert_eq!(messages.borrow()[0], "Message 1");
assert_eq!(messages.borrow()[1], "Message 2");

ws_mock.uninstall().expect("Failed to uninstall WebSocket mock");
}

Tips and Best Practices

  1. Always call uninstall(): Make sure to uninstall the mock when your test is complete to restore the original WebSocket implementation.

  2. Clear between tests: Use ws_mock.clear() to reset recorded connections and messages between tests.

  3. Test error handling: Use simulate_connection_error() and trigger_error() to ensure your code properly handles connection failures.

  4. Validate message formats: Use message validation to ensure your application is sending correctly formatted data.

  5. Test reconnection logic: Deliberately close connections to test that your reconnection logic works correctly.

  6. Simulate realistic scenarios: Configure delays and connection behavior to mimic real-world conditions.

Troubleshooting

Mock not intercepting connections

Make sure you call install() before any WebSocket connection is created, and that it returns Ok(()).

Test timing issues

Use the wait_for() pattern shown in the test examples to ensure asynchronous operations complete before making assertions:

async fn wait_for<F: Fn() -> bool>(condition: F, timeout_ms: u32) -> bool {
let start = js_sys::Date::now();

while !condition() {
if js_sys::Date::now() - start > timeout_ms as f64 {
return false;
}

// Small delay
let _ = JsFuture::from(Promise::new(&mut |resolve, _| {
window().set_timeout_with_callback_and_timeout_and_arguments_0(
Closure::once_into_js(move || resolve.call0(&JsValue::NULL).unwrap()).as_ref().unchecked_ref(),
10,
).unwrap();
})).await;
}

true
}

Binary data handling

The WebSocket mock supports binary data handling, but for detailed validation of binary content, you may need to extend the current implementation.

Advanced Customization

If you need additional functionality, the WebSocket mock is designed to be extensible. Some ideas for customization:

  1. Add support for detailed binary data validation
  2. Implement network condition simulation (latency, packet loss)
  3. Add recording and playback capabilities for WebSocket sessions
  4. Extend validation to support more complex schema formats