GameMaker Testing Library - Catch Bugs Early and Improve Your Game
- GameMaker
- 05 February 2025
- General

GameMaker Testing Library is an award-winning GameMaker tool. Big thanks to its developer DAndrëwBox, who wrote this tutorial himself and makes learning how to use it simple.
What is GameMaker Testing Library?
GameMaker Testing Library (GMTL) helps you test your GameMaker projects with ease, whether you're a tool developer or a game developer. You can use it to verify that your libraries, extensions, prefabs, game logic, functions, and objects work exactly as expected. With GMTL, you can ensure reliability and catch potential issues early, saving you time and effort during development.
If you have some knowledge in web development, you may be familiar with popular testing frameworks like Jest or React Testing Library. GMTL takes inspiration from them, but it's designed to work using only GameMaker native functions and features.
So, why should you use it?
- Simplify Testing: GMTL removes the guesswork by providing clear, structured results for the behavior of your scripts.
- Improve Codebase Confidence: Whether you're testing a script or an object's behavior, GMTL ensures your code works as intended, even after making big changes
- Save Time In The Long Run: Automating repetitive testing processes allows you to focus on creating and refining your game or tools.
- Free and Open-source: Since GMTL is created for and with GameMaker native functions, no DLLs or external dependencies are needed, and its code cannot be hidden.
What can I test with GMTL?
Let's say you're developing a platformer game, and you've written a script that calculates the player's score based on items collected and enemies defeated. Manually verifying the calculations each time you make a change can be tedious and error-prone.
With GMTL, you can automate this process by writing a simple test suite. For example, consider a script called calculate_score:
/// @function calculate_score(items, enemies)
/// @param {real} items - The number of items collected.
/// @param {real} enemies - The number of enemies defeated.
/// @returns {real} - The total score.
function calculate_score(items, enemies) {
var _min_score = 0;
var _current_score = (items * 10) + (enemies * 20);
return max(_min_score, _current_score);
}Using GMTL, you could write a test suite like this:
suite(function() {
section("calculate_score", function() {
test("Items should add 10 score each", function() {
expect(calculate_score(1, 0)).toBe(10);
expect(calculate_score(5, 0)).toBe(50);
expect(calculate_score(10, 0)).toBe(100);
});
test("Enemies should add 20 score each", function() {
expect(calculate_score(0, 1)).toBe(20);
expect(calculate_score(0, 5)).toBe(100);
expect(calculate_score(0, 10)).toBe(200);
});
test("10 items and 5 enemies = 200", function() {
expect(calculate_score(10, 5)).toBe(200);
});
test("No items and no enemies = 0", function() {
expect(calculate_score(0, 0)).toBe(0);
});
test("Edge case: Score cannot be negative.", function() {
expect(calculate_score(-1, -1)).toBe(0);
});
});
});When you run the tests, GMTL will output results to the console, clearly showing which tests passed or failed. This makes it easier to spot errors and refine your logic.
If in the future you decide that enemies will not give 20 score points but 25, you may probably need to do a manual test again, run the game, then play a short round in your game and then let the score be calculated to check everything is working as expected. Using GMTL you only need to change the test suite according to the new expected results and just run the project.
What Else Can You Test?
Beyond just scripts, GMTL also allows you to test:
- Object behaviors and properties: Simulate user interactions, frame updates, object events, and ensure objects behave as expected in multiple scenarios.
- Complex scenarios: Test interactions between multiple objects, simulate keypresses, gamepad buttons or mouse clicks, and validate timing-dependent behaviors.
By automating your testing process, GMTL lets you focus on what matters most: creating an amazing tool, library or game.
Versions & Compatibility
The minimum version of GameMaker required to use this library is GameMaker 2023.4. This is because it heavily uses the string templates and mostly string functions introduced in that version and later like string_split().
The library is fully compatible with the latest version of GameMaker 2024 and will be updated as needed for future versions.

How To Add GMTL To Your Project
GMTL is very simple to add to your project. You don't need any previous setup or configuration, just plug and test!
To add GMTL to your project, follow these steps:
- Download the latest version of GMTL from the Github releases page.
- Inside GameMaker, click on Tools > Import Local Package... and select the .yymps file you downloaded.
- Add the whole library to your project's resources. (Or skip the "Demo" folder if you already know how to use it).
- You are done! You can start writing your tests right away!

Your First Test Suite
GameMaker Testing Library is simple to use, but it's important to understand how it works first. GMTL uses a structure of suites, sections/describe, and tests/it to organize and run your tests.
This means that you will need to create a suite, add sections to it, and then add tests to those sections which will not share any data to the outside, ensuring that each test, section and suite is isolated and independent from each other.
Each test should run and check if the expected result is the same as the actual result and each test will be marked as passed or failed depending on the result.
To create your first test suite, you should have already one function or object you want to test, then follow these steps:
- Create a new empty script in your project. (recommended)
- Add the suite function to the script, this will be the main structure of your tests.
suite(function() {
// Your section will be inserted here!
});- Find the script you want to test, for a simple example, a script that adds two numbers.
/// @function add_two_numbers(a, b)
/// @param {real} a
/// @param {real} b
/// @returns {real}
function add_two_numbers(a, b) {
return a + b;
}- Add a section to the suite, this will be the place where you will add your tests (I like to name the sections with the function or object I'm testing). The first parameter is the name of the section, and the second parameter is a function that will contain your tests.
suite(function() {
section("add_two_numbers", function() {
// Your tests will be inserted here!
});
});- Add some tests to the section, each test should have a name and a function that will contain the test itself. The expect function returns an object and this is used to check if the actual result is the same as the expected result. We would add 3 tests to the section:
suite(function() {
section("add_two_numbers", function() {
test("1 + 1 = 2", function() {
expect(add_two_numbers(1, 1)).toBe(2);
});
test("2 + 2 = 4", function() {
var _value = add_two_numbers(2, 2);
expect(_value).toBe(4);
expect(_value).toBeLessThan(5);
});
test("2 + 2 != 10", function() {
expect(add_two_numbers(1, 2)).never().toBe(10);
});
});
});- Run the project and see the results in the console. You should see something like this:
------- add_two_numbers -------
✔ 1 + 1 = 2 (0.01ms)
✔ 2 + 2 = 4 (0.01ms)
✔ 2 + 2 != 10 (0.01ms)
================================================================
======================= Tests Finished! ========================
Test Suites: 1 passed, 1 total. (100% success)
Tests: 3 passed, 3 total. (100% success)
All tests finished in 0.03ms.- Done! You have created your first test suite using GameMaker Testing Library.
Testing Something Bigger!
So, that was a very simple example of how to use GMTL, but you can test something bigger and more complex. As you read, you can also test objects, properties, functions, scripts, and more.
So, let's say we have an object called obj_timer and it has a variable timer that increases every step while this object exists and destroys itself after 200 frames. We can test from it's creation to its destruction to see if the timer is increasing as expected.
So, we first create a suite and section for the object.
suite(function() {
describe("obj_timer", function() {
// Your tests will be inserted here!
}
}When we want to add a way to test the object and execute simulations inside them, we would use the create function to create the object. This function will return the instance id of the object created and works exactly like the old instance_create function.
test("Should create an instance, wait, and check alive timer.", function() {
var _inst = create(10, 10, o_gmtl_demo_timer);
// Check if the instance was created
expect(instance_exists(_inst)).toBeTruthy();
});We now have to compliment this test with a frame wait simulation to let the object run and increase the timer. We can use the waitFor function to wait for a number of frames or seconds before running the next steps of the test. This will trigger an event update for all events in this object.
test("Should create an instance, wait, and check alive timer.", function() {
var _inst = create(10, 10, o_gmtl_demo_timer);
// Check if the instance was created
expect(instance_exists(_inst)).toBeTruthy();
// Wait for 5 frames
_inst.waitFor(5, time_source_units_frames);
expect(_inst).toHaveProperty("timer", 5);
// Wait for 2 seconds (120 frames)
_inst.waitFor(2, time_source_units_seconds);
// Assuming gamespeed to be 60 fps / sec, so 5 + (60 frames * 2 seconds)
expect(_inst.timer).toBeEqual(125);
});As you see, we can use expect using the toHaveProperty() function, or directly referencing it, but, it's safest and error free to use the toHaveProperty() function to check if the object has the property we are looking for.
Now, we can add some more wait and a test to check if the object is destroyed after its timer reaches 200 or more. We can use the destroy function to destroy the object and check if it was destroyed.
test("Should create an instance, wait, and check alive timer and destruction.", function() {
var _inst = create(10, 10, o_gmtl_demo_timer);
// Check if the instance was created
expect(instance_exists(_inst)).toBeTruthy();
// Wait for 5 frames
_inst.waitFor(5, time_source_units_frames);
expect(_inst).toHaveProperty("timer", 5);
// Wait for 2 seconds (120 frames)
_inst.waitFor(2, time_source_units_seconds);
// Assuming gamespeed to be 60 fps / sec, so 5 + (60 frames * 2 seconds)
expect(_inst.timer).toBeEqual(125);
// Check if the instance is still alive
expect(instance_exists(_inst)).toBeTruthy();
// Wait for 75 frames
_inst.waitFor(75, time_source_units_frames);
// Check if the instance was destroyed
expect(instance_exists(_inst)).toBeFalsy();
});Then, you can run the project and see the results in the console. You should see something like this:
------- obj_timer -------
✔ Should create an instance, wait, and check alive timer. (1.69ms)
======================= Tests Finished! ========================
Test Suites: 1 passed, 1 total. (100% success)
Tests: 1 passed, 1 total. (100% success)
All tests finished in 1.69ms.And that's it! You have created a test suite for a more complex object using GameMaker Testing Library.
If we wanted it, we could also have some keyboard checks to modify the time using the simulateKey* functions, or simulate mouse clicks using the simulateMouse* functions, or even, simulate two timers with more objects on screen and check if they are working as expected by using functions like simulateFrameWait. The limit is your imagination! (Not really, the limit is the GameMaker functions and features you can use).
Troubleshooting and Documentation
If you have any issues or questions about the library, you can check the Troubleshooting page for common issues and solutions, or visit the issues tracker in Github to add or check if an issue you have exists there!
If you need more information about the library and functions to use, you can check the Documentation page for more details.
Thanks for reading and happy testing!
Back to Tutorials