Skip to main content

Testing with @rpgjs/testing

The @rpgjs/testing package provides utilities to test your RPGJS modules and game logic in a controlled environment. It sets up both server and client instances, allowing you to test player interactions, server hooks, and game mechanics.

Installation

The @rpgjs/testing package is already included in RPGJS projects. If you need to install it separately:

Setup

Vitest Configuration

To use the testing utilities, configure Vitest to use the setup file provided by @rpgjs/testing. This setup file automatically mocks WebGL, images, and media elements for testing in a Node.js environment.
The setup file automatically:
  • Mocks WebGL canvas using vitest-webgl-canvas-mock
  • Mocks image loading
  • Mocks HTML media elements (audio/video)
  • Creates a DOM container for the game

Basic Usage

Simple Test

The simplest way to test is to use the testing() function without any modules:

Understanding the Testing Fixture

The testing() function returns a fixture object with a createClient() method:
The client object provides access to:
  • server: The RpgServer instance
  • socket: The WebSocket connection
  • client: The RpgClientEngine instance
  • playerId: The unique identifier of the player
  • player: A getter that returns the RpgPlayer instance

Testing with Custom Modules

You can test your custom modules by passing them to the testing() function. The recommended way is to use createModule with an object containing server and client properties:

Advanced Configuration

Server Configuration

You can pass custom server configuration:

Client Configuration

You can pass custom client configuration:

Complete Example

Testing Player Actions

You can test player actions and server hooks:

Testing Events and Hooks

Test server-side hooks and events:

Testing Map Changes

You can test map changes and verify that players correctly transition between maps. The testing fixture provides a waitForMapChange() helper method to wait for map transitions.

Basic Map Change Test

First, define maps in your server module and set up an initial map change:

Using waitForMapChange()

The waitForMapChange() method is available on the client object returned by createClient(). It:
  • Polls the player’s current map until it matches the expected map ID
  • Returns a Promise that resolves with the updated player instance
  • Throws an error if the timeout is exceeded (default: 5000ms)
Important: Always assign the returned value to your player variable, as it returns an updated player instance:

Testing Map Hooks

You can test hooks that fire when players join maps:

Testing Multiple Clients

You can create multiple clients to test multiplayer scenarios:

Helper Functions

provideTestingLoadMap()

This function provides a mock map loader for testing. It’s automatically included when using testing(), but you can use it directly if needed:

waitForSyncComplete()

Waits for server-client synchronization to complete. Useful when testing client-side state after server-side changes:

Cleanup and Test Isolation

Using fixture.clear()

It’s recommended to call fixture.clear() in an afterEach hook to ensure proper cleanup between tests. This clears all server and client instances, caches, and resets the DOM:
This ensures:
  • No state leaks between tests
  • All server and client instances are properly destroyed
  • DOM is reset to a clean state
  • Injection contexts are cleared

Testing Database Items

Defining Items in Module Database

You can define items, weapons, and armors directly in your server module’s database property:

Adding Items Dynamically to Maps

You can also add items to a map’s database dynamically using addInDatabase():

Synchronization Utilities

waitForSyncComplete()

When you make server-side changes (like adding items, changing player state), you may need to wait for the synchronization to complete before testing client-side state:

Best Practices

  1. Use beforeEach: Create a fresh fixture and client for each test to ensure isolation
  2. Use afterEach with fixture.clear(): Always clean up after each test to prevent state leaks
  3. Test modules separately: Create focused tests for individual modules
  4. Use async/await: The testing() and createClient() functions are async
  5. Wait for map changes: Use waitForMapChange() after onConnected or changeMap() calls
  6. Assign player from waitForMapChange(): Always assign the returned value to get the updated player instance
  7. Mock external dependencies: Use Vitest mocks for external services or APIs
  8. Define items in database: Use the module’s database property for test items

Example: Complete Test Suite

Customizing Map Configuration in Tests

By default, testing() uses provideTestingLoadMap() which provides maps with default dimensions (1024x768) and a minimal mock component. If you need custom map configuration, you can pass your own provideLoadMap in clientConfig.providers:
If you don’t provide provideLoadMap, the default provideTestingLoadMap() will be used automatically.

Troubleshooting

Tests fail with WebGL errors

Make sure you’ve configured Vitest to use the setup file from @rpgjs/testing:

Player is undefined

Ensure you’re calling createClient() and accessing client.player after the client is created:

Module hooks not being called

Verify that your modules are correctly structured and passed to the testing() function:

Map component errors

If you see errors about missing map components or hitbox requirements, make sure you’re either:
  • Using the default provideTestingLoadMap() (automatically added if no custom provideLoadMap is provided)
  • Or providing a complete provideLoadMap with component, width, height, and data properties