agentsclimarketplace

Minecraft testing

Skill ComeOnOliver/skillshub/skills/Jahrome907/minecraft-codex-skills/minecraft-testing

🧠 The right skill, one API call. AI agent skills registry with token-efficient skill resolution. 5,000+ skills from 500+ top repos.

Install
npx -y skills add ComeOnOliver/skillshub --skill minecraft-testing

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

Write automated tests for Minecraft mods and plugins for 1.21.x. Covers NeoForge GameTests (@GameTest annotation, GameTestHelper assertions, test structure placement), Fabric game tests (fabric-gametest-api-v1), unit testing non-Minecraft logic with JUnit 5, MockBukkit for Paper/Bukkit plugin testing (mock server, mock player, event dispatching, inventory checking), integration testing with a test server via Gradle, and GitHub Actions CI workflows that run GameTests headlessly. Includes patterns for mocking registries, testing event handlers, testing commands, and test-driven development for Minecraft projects.

SKILL.md

12.1 KB, ~2.8k tokens by cl100k_base, as published. Nobody here has run it

Minecraft Testing Skill

Testing Strategies Overview

ApproachBest ForRequires Game?
JUnit 5 (pure unit tests)Logic, data structures, NBT serializationNo
MockBukkitBukkit/Paper plugin events, commands, inventoryNo (mocked server)
NeoForge GameTestsIn-game block/entity/world interactionYes (test environment)
Fabric GameTestsIn-game block/entity/world interactionYes (test environment)
Integration serverFull plugin/mod lifecycleYes (dedicated test server)

Routing Boundaries

  • Use when: the task is designing or implementing automated tests (unit, mock, gametest, CI test jobs) for Minecraft projects.
  • Do not use when: the task is implementing gameplay features rather than testing them (minecraft-modding, minecraft-plugin-dev, minecraft-datapack).
  • Do not use when: the task is release automation or publishing pipelines (minecraft-ci-release).

Unit Testing (JUnit 5 — No Minecraft)

build.gradle.kts additions

dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter:5.11.0")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.test {
    useJUnitPlatform()
    testLogging {
        events("passed", "skipped", "failed")
    }
}

Example pure unit test

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CooldownManagerTest {

    @Test
    void playerOnCooldown_returnsFalse_afterExpiry() {
        var manager = new CooldownManager(500L); // 500ms cooldown
        manager.startCooldown("steve");
        assertTrue(manager.isOnCooldown("steve"));
        // fast-forward time by sleeping or injecting a Clock
        assertFalse(manager.isOnCooldown("notExisting"));
    }

    @Test
    void cooldown_throwsIllegalArgument_onNegativeDuration() {
        assertThrows(IllegalArgumentException.class,
            () -> new CooldownManager(-1L));
    }
}

MockBukkit (Paper/Bukkit Plugin Tests)

MockBukkit provides a mock Bukkit server for unit-testing plugin logic without running a real Minecraft server.

build.gradle.kts

repositories {
    maven("https://repo.papermc.io/repository/maven-public/")
    maven("https://repo.mockbukkit.org/artifactory/mockbukkit/")
}

dependencies {
    compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
    testImplementation("org.junit.jupiter:junit-jupiter:5.11.0")
    testImplementation("com.github.seeseemelk:MockBukkit-v1.21:3.127.0")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.test {
    useJUnitPlatform()
}

Setup / teardown pattern

import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import be.seeseemelk.mockbukkit.entity.PlayerMock;
import org.junit.jupiter.api.*;

class MyPluginTest {

    private static ServerMock server;
    private static MyPlugin plugin;

    @BeforeAll
    static void setUp() {
        // Start mock Bukkit server and load your plugin
        server = MockBukkit.mock();
        plugin = MockBukkit.load(MyPlugin.class);
    }

    @AfterAll
    static void tearDown() {
        MockBukkit.unmock();
    }

    @BeforeEach
    void beforeEach() {
        // Create a fresh mock player per test if needed
    }
}

Testing events

@Test
void playerJoin_getsWelcomeMessage() {
    PlayerMock player = server.addPlayer("Steve");
    player.simulateJoin(); // fires PlayerJoinEvent

    // Assert the player received the expected message component
    player.assertSaid("Welcome, Steve!");
    // Or for Adventure components:
    assertTrue(player.nextMessage().contains("Welcome"));
}

@Test
void onBlockBreak_cancelledForNonOp() {
    PlayerMock player = server.addPlayer();
    player.setOp(false);

    Block block = player.getWorld().getBlockAt(0, 64, 0);
    block.setType(Material.STONE);
    BlockBreakEvent event = new BlockBreakEvent(block, player);
    server.getPluginManager().callEvent(event);

    assertTrue(event.isCancelled(), "Non-op should not be able to break blocks");
}

Testing commands

@Test
void mypluginInfo_returnsVersion() {
    PlayerMock player = server.addPlayer("Admin");
    player.setOp(true);

    boolean result = server.dispatchCommand(player, "myplugin info");

    assertTrue(result);
    player.assertSaid("Version: " + plugin.getDescription().getVersion());
}

@Test
void mypluginReload_requiresOp() {
    PlayerMock player = server.addPlayer("NonOp");
    player.setOp(false);

    server.dispatchCommand(player, "myplugin reload");

    player.assertSaid("No permission.");
}

Testing inventory / items

@Test
void giveKitCommand_givesPlayerItems() {
    PlayerMock player = server.addPlayer();
    
    server.dispatchCommand(player, "kit starter");
    
    // Check inventory
    assertTrue(player.getInventory().contains(Material.STONE_SWORD));
    assertTrue(player.getInventory().contains(Material.BREAD, 16));
}

Testing scheduler tasks

@Test
void repeatingTask_firesAfterDelay() {
    PlayerMock player = server.addPlayer();
    
    // Execute 40 ticks worth of scheduled tasks
    server.getScheduler().performTicks(40L);
    
    // Assert expected side effect happened
    assertEquals(2, plugin.getTaskCount());
}

Testing PDC

@Test
void pdcKillCount_incrementsOnKill() {
    PlayerMock player = server.addPlayer();
    NamespacedKey key = new NamespacedKey(plugin, "kills");
    
    // Simulate kill event
    EntityDeathEvent deathEvent = new EntityDeathEvent(
        server.addMockEntity(EntityType.ZOMBIE), new ArrayList<>(), 0
    );
    deathEvent.getEntity().setKiller(player);
    server.getPluginManager().callEvent(deathEvent);
    
    int kills = player.getPersistentDataContainer()
        .getOrDefault(key, PersistentDataType.INTEGER, 0);
    assertEquals(1, kills);
}

NeoForge GameTests

GameTests run inside a Minecraft world. They place a structure (the test environment), then run assertions using GameTestHelper.

Registration

// In your mod main class:
@Mod(MyMod.MOD_ID)
public class MyMod {
    public MyMod(IEventBus modEventBus) {
        modEventBus.register(MyGameTests.class);
    }
}

Test class

import net.minecraft.gametest.framework.*;
import net.neoforged.neoforge.gametest.GameTestHolder;
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;

@GameTestHolder(MyMod.MOD_ID)                // registers test namespace
@PrefixGameTestTemplate(false)               // don't prefix template names
public class MyGameTests {

    // Default template: 3x3x3 air structure called "mymod:empty"
    @GameTest(template = "mymod:empty")
    public static void testBlockInteraction(GameTestHelper helper) {
        // Place a block
        helper.setBlock(1, 1, 1, net.minecraft.world.level.block.Blocks.FURNACE);
        
        // Run after 1 tick
        helper.runAfterDelay(1, () -> {
            // Assert block state
            helper.assertBlock(new net.minecraft.core.BlockPos(1, 1, 1),
                b -> b.is(net.minecraft.world.level.block.Blocks.FURNACE),
                "Expected furnace");
            
            helper.succeed();
        });
    }

    @GameTest(template = "mymod:empty", timeoutTicks = 200)
    public static void testEntitySpawn(GameTestHelper helper) {
        // Spawn entity
        var entity = helper.spawnWithNoFreeWill(
            net.minecraft.world.entity.EntityType.ZOMBIE, new net.minecraft.core.BlockPos(2, 2, 2)
        );
        
        helper.runAfterDelay(5, () -> {
            helper.assertEntityPresent(
                net.minecraft.world.entity.EntityType.ZOMBIE,
                new net.minecraft.core.BlockPos(2, 2, 2), 1.0
            );
            helper.succeed();
        });
    }
}

Structure templates (.nbt files)

Place empty structure files at:
src/main/resources/data/mymod/structures/empty.nbt

Generate them in-game using /test create mymod:empty 3 3 3 (NeoForge test command). Commit the .nbt files to version control.

Running GameTests

# Start the test server and run all tests
./gradlew runGameTestServer

# In-game (dev environment):
# /test runall
# /test run mymod:test_block_interaction

Fabric GameTests

import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.level.block.Blocks;

public class MyFabricGameTests implements FabricGameTest {

    @GameTest(template = EMPTY_STRUCTURE)
    public void testCustomBlock(GameTestHelper helper) {
        helper.setBlock(1, 1, 1, Blocks.GOLD_BLOCK.defaultBlockState());
        
        helper.runAfterDelay(2, () -> {
            helper.assertBlock(
                new BlockPos(1, 1, 1),
                b -> b.is(Blocks.GOLD_BLOCK),
                "Gold block should be placed"
            );
            helper.succeed();
        });
    }
}

Register in fabric.mod.json

{
  "entrypoints": {
    "fabric-gametest": [
      "com.example.mymod.fabric.MyFabricGameTests"
    ]
  }
}

GameTestHelper Assertions Reference

// Block assertions
helper.assertBlock(pos, predicate, "message");
helper.assertBlockState(pos, state -> state.is(Blocks.STONE), "Expected stone");
helper.assertBlockPresent(Blocks.GOLD_BLOCK, pos);
helper.assertBlockNotPresent(Blocks.TNT, pos);

// Entity assertions
helper.assertEntityPresent(EntityType.ZOMBIE, pos, radius);
helper.assertEntityNotPresent(EntityType.ZOMBIE);
helper.assertEntityCount(EntityType.ZOMBIE, expectedCount);
helper.assertEntityProperty(entity, entity -> entity.getHealth() > 0, "alive");

// Item assertions
helper.assertContainerContains(pos, Items.DIAMOND);
helper.assertContainerEmpty(pos);

// Control flow
helper.succeed();        // mark test as passed — REQUIRED at end
helper.fail("reason");   // mark test as failed
helper.runAfterDelay(ticks, runnable); // schedule assertion
helper.onEachTick(runnable);          // run every tick (use with care)
helper.succeedWhen(() -> { /* assertions */ }); // poll until assertions pass or timeout
helper.succeedOnTickWhen(tick, () -> { /* assertions */ });

CI: Running Tests in GitHub Actions

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - uses: gradle/actions/setup-gradle@v3
      - name: Run unit tests
        run: ./gradlew test

  game-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - uses: gradle/actions/setup-gradle@v3
      - name: Run GameTests (headless)
        run: ./gradlew runGameTestServer
        env:
          # Required for headless rendering
          CI: true

References

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.