Skip to main content
Version: Minecraft 26.2

In-game tests

Poly Mod Loader mods are tested in the real game on Mojang's game test server. No window opens, so you can run them in CI too. The game test server decides the results.

Register tests​

Register tests in the common entrypoint. The ID is <mod id>:<name>, and each test gets an empty area. (0, 0, 0) is the lowest corner of that area.

mod.gameTests().register("ruby_block_drops", test -> {
BlockPos at = BlockPos.of(0, 1, 0);
test.setBlock(at, ResourceId.of("ruby_mod", "ruby_block"));
test.assertBlock(at, ResourceId.of("ruby_mod", "ruby_block"));
int dropped = test.runCommand("loot spawn ~ ~2 ~ loot ruby_mod:blocks/ruby_block");
test.assertTrue(dropped == 1, "the loot table drops 1 item");
test.succeed();
});
Always call succeed()

A test passes only if it calls succeed(). If an assertion fails, or the test does not finish in time, it is reported as failed.

Registration forms​

MethodMeaning
register(name, test)Default time, an area of one block
register(name, maxTicks, test)Sets the maximum number of ticks
register(name, maxTicks, area, test)Sets the maximum ticks and the test area (TestArea)

An invalid test name or time gives POLY-GAMETEST-001. Registering after registration has closed gives POLY-GAMETEST-002.

What you can use in a test​

These are the methods that GameTestContext (test in the examples above) provides.

MethodUse
world(), absolute(pos)The test world, and converting relative coordinates to world coordinates
setBlock(pos, block), assertBlock(pos, block)Place and check a block (relative coordinates)
assertTrue(condition, message), fail(message)Assertions and failure
createMockPlayer()A player with no connection (items, block use, menus)
createMockServerPlayer()For features that need a server player (advancements, permissions, the interaction helpers below)
createConnectedMockPlayer()A survival-mode player that joins over an in-memory connection, like a client (PlayerEvents.JOINED fires)
respawn(player, returnedFromEnd)Respawns a connected mock player as its client asks from the death screen or after the End credits, and returns the new player
disconnect(player)Disconnects a connected mock player (PlayerEvents.LEAVING fires)
useBlock(pos, player)The player uses a block with an empty hand
interactBlock(player, pos, face)A server player right-clicks a face of a block with its main hand, as the server handles a client's click. Returns the InteractionResult
interactItem(player)A server player uses the item in its main hand in the air
interactEntity(player, target)A server player right-clicks an entity with its main hand
attackEntity(player, target)A server player attacks an entity, as a left-click does
breakBlock(player, pos)A server player breaks a block at once, as when it finishes digging. Returns whether the block was broken
runCommand(command)Runs a server command at the test location and returns the result value
runAfter(ticks, action)Continues checking after some ticks
succeed()Pass

world() uses world coordinates. Convert with absolute, as in test.world().blockAt(test.absolute(pos)).

Example: give an item​

mod.gameTests().register("ruby_can_be_given", test -> {
PlayerView player = test.createMockPlayer();
player.give(ResourceId.of("ruby_mod", "ruby"), 2);
test.assertTrue(player.countItem(ResourceId.of("ruby_mod", "ruby")) == 2, "the player holds 2 rubies");
test.succeed();
});

Example: use a block​

mod.gameTests().register("using_glow_block_gives_spark", test -> {
test.setBlock(BlockPos.of(0, 1, 0), GLOW_BLOCK);
PlayerView player = test.createMockPlayer();
test.useBlock(BlockPos.of(0, 1, 0), player);
test.assertTrue(player.countItem(SPARK) == 1, "expected 1 spark, found " + player.countItem(SPARK));
test.succeed();
});

Example: command result​

mod.gameTests().register("command_returns_sum", test -> {
int result = test.runCommand("showcase add 2 3");
test.assertTrue(result == 5, "/showcase add 2 3 returned " + result);
test.succeed();
});

Example: interactions like a client​

interactBlock, interactItem, interactEntity, attackEntity, and breakBlock make a server player act the way the server handles input from a connected client. Interaction and block events fire first, then the game's own checks and behavior. Unlike useBlock, interactBlock also places a held block item. They need a player from createMockServerPlayer() (or createConnectedMockPlayer()). Other players throw IllegalArgumentException.

mod.gameTests().register("break_event_cancels_breaking", 20, new TestArea(3, 3, 1), test -> {
BlockPos ward = BlockPos.of(0, 1, 0);
test.setBlock(ward, WARDED_BLOCK);
PlayerView player = test.createMockServerPlayer();
test.assertTrue(!test.breakBlock(player, ward), "an empty hand cannot break the warded block");
test.assertBlock(ward, WARDED_BLOCK);
player.give(SPARK, 1);
test.assertTrue(test.breakBlock(player, ward), "a spark breaks the warded block");
test.succeed();
});

Example: respawning​

A player from createConnectedMockPlayer() respawns through respawn. The game builds a new player object, PlayerEvents.COPY and RESPAWNED fire, and respawn returns the new player. Respawning after death throws IllegalStateException while the player is still alive.

mod.gameTests().register("respawn_fires_copy_and_respawned", 40, test -> {
PlayerView player = test.createConnectedMockPlayer();
player.hurt(ResourceId.minecraft("generic_kill"), 50); // like /kill
PlayerView respawned = test.respawn(player, false); // false: after death
int count = respawned.data(respawns).data().getInt("count", 0);
test.assertTrue(count == 1, "the new player counted 1 respawn, found " + count);
test.disconnect(respawned);
test.succeed();
});
Disconnect connected players before the test returns

While connected, the player affects the whole test world like a real player: mobs far from every player despawn, and mobs spawn near it. That disturbs other tests running at the same time. Call disconnect before the test function returns. Players still connected when the test ends are disconnected then.

ShowcaseGameTests in examples/native-showcase-mod has tests for every API area.

Next​

Buy me a coffee