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.
- 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 thetesting() function without any modules:
Understanding the Testing Fixture
Thetesting() function returns a fixture object with a createClient() method:
client object provides access to:
server: The RpgServer instancesocket: The WebSocket connectionclient: The RpgClientEngine instanceplayerId: The unique identifier of the playerplayer: A getter that returns the RpgPlayer instance
Testing with Custom Modules
You can test your custom modules by passing them to thetesting() 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 awaitForMapChange() 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)
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:
- 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’sdatabase property:
Adding Items Dynamically to Maps
You can also add items to a map’s database dynamically usingaddInDatabase():
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
- Use
beforeEach: Create a fresh fixture and client for each test to ensure isolation - Use
afterEachwithfixture.clear(): Always clean up after each test to prevent state leaks - Test modules separately: Create focused tests for individual modules
- Use async/await: The
testing()andcreateClient()functions are async - Wait for map changes: Use
waitForMapChange()afteronConnectedorchangeMap()calls - Assign player from
waitForMapChange(): Always assign the returned value to get the updated player instance - Mock external dependencies: Use Vitest mocks for external services or APIs
- Define items in database: Use the module’s
databaseproperty 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:
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 callingcreateClient() and accessing client.player after the client is created:
Module hooks not being called
Verify that your modules are correctly structured and passed to thetesting() 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 customprovideLoadMapis provided) - Or providing a complete
provideLoadMapwithcomponent,width,height, anddataproperties