{"slug":"minecraft-testing-3","title":"minecraft-testing","summary":"Design and implement automated tests for current Minecraft 26.x or legacy 1.21.x mods and plugins using JUnit, MockBukkit, NeoForge Game Tests, or Fabric Game Tests. Use for test code and test execution, not release publishing or gameplay implementation.","platform":"Codex CLI","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-07T18:38:50.988448Z","repo":{"url":"https://github.com/Jahrome907/minecraft-agent-skills","stars":151,"forks":9,"license":"MIT","updatedAt":"2026-09-13T05:52:30Z"},"bodyHtml":"<hr>\n<h2>name: minecraft-testing\ndescription: \"Design and implement automated tests for current Minecraft 26.x or legacy 1.21.x mods and plugins using JUnit, MockBukkit, NeoForge Game Tests, or Fabric Game Tests. Use for test code and test execution, not release publishing or gameplay implementation.\"</h2>\n<h1>Minecraft Testing Skill</h1>\n<h2>Testing Strategies Overview</h2>\n<table>\n<thead>\n<tr>\n<th>Approach</th>\n<th>Best For</th>\n<th>Requires Game?</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>JUnit 5</strong> (pure unit tests)</td>\n<td>Logic, data structures, NBT serialization</td>\n<td>No</td>\n</tr>\n<tr>\n<td><strong>MockBukkit</strong></td>\n<td>Bukkit/Paper plugin events, commands, inventory</td>\n<td>No (mocked server)</td>\n</tr>\n<tr>\n<td><strong>NeoForge GameTests</strong></td>\n<td>In-game block/entity/world interaction</td>\n<td>Yes (test environment)</td>\n</tr>\n<tr>\n<td><strong>Fabric GameTests</strong></td>\n<td>In-game block/entity/world interaction</td>\n<td>Yes (test environment)</td>\n</tr>\n<tr>\n<td><strong>Integration server</strong></td>\n<td>Full plugin/mod lifecycle</td>\n<td>Yes (dedicated test server)</td>\n</tr>\n</tbody>\n</table>\n<p>Use Java 25 for current 26.x projects. Keep legacy 1.21.x examples on Java 21\nand Forge 1.20.1 on Java 17. Do not combine source layouts or APIs across lanes.</p>\n<h3>Routing Boundaries</h3>\n<ul>\n<li><code>Use when</code>: the task is designing or implementing automated tests (unit, mock, gametest, CI test jobs) for Minecraft projects.</li>\n<li><code>Do not use when</code>: the task is implementing gameplay features rather than testing them (<code>minecraft-modding</code>, <code>minecraft-plugin-dev</code>, <code>minecraft-datapack</code>).</li>\n<li><code>Do not use when</code>: the task is release automation or publishing pipelines (<code>minecraft-ci-release</code>).</li>\n</ul>\n<h2>Bundled References And Helpers</h2>\n<ul>\n<li>Layout guide: <code>references/test-layouts.md</code></li>\n<li>Fixture/layout validator: <code>./scripts/validate-test-layout.sh --root &lt;project&gt;</code></li>\n</ul>\n<p>Use the validator before copying a test layout into a real project. It checks\nvisible static dependencies, metadata, and literal structure references. It does\nnot compile the project or prove a Game Test can run.</p>\n<hr>\n<h2>Unit Testing (JUnit 5 — No Minecraft)</h2>\n<h3>JUnit Platform task</h3>\n<pre><code>tasks.test {\n    useJUnitPlatform()\n    testLogging {\n        events(\"passed\", \"skipped\", \"failed\")\n    }\n}\n</code></pre>\n<p>For Fabric code that needs loader setup, use Fabric Loader JUnit rather than\nassuming ordinary JUnit initialized Minecraft:</p>\n<pre><code>dependencies {\n    testImplementation \"net.fabricmc:fabric-loader-junit:${project.loader_version}\"\n}\n\ntest {\n    useJUnitPlatform()\n}\n</code></pre>\n<p>When a unit test reaches registry-dependent Minecraft classes, initialize only\nthe required bootstrap in test setup. The current Fabric guide uses\n<code>SharedConstants.tryDetectVersion()</code> and <code>Bootstrap.bootStrap()</code> for that case.</p>\n<h3>Example pure unit test</h3>\n<pre><code>import org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass CooldownManagerTest {\n\n    @Test\n    void cooldown_tracksPlayersIndependently() {\n        var manager = new CooldownManager(500L); // 500ms cooldown\n        manager.startCooldown(\"steve\");\n        assertTrue(manager.isOnCooldown(\"steve\"));\n        assertFalse(manager.isOnCooldown(\"notExisting\"));\n    }\n\n    @Test\n    void cooldown_throwsIllegalArgument_onNegativeDuration() {\n        assertThrows(IllegalArgumentException.class,\n            () -&gt; new CooldownManager(-1L));\n    }\n}\n</code></pre>\n<hr>\n<h2>MockBukkit (Paper/Bukkit Plugin Tests)</h2>\n<h3><code>build.gradle.kts</code></h3>\n<pre><code>repositories {\n    maven(\"https://repo.papermc.io/repository/maven-public/\")\n    mavenCentral()\n}\n\ndependencies {\n    compileOnly(\"io.papermc.paper:paper-api:26.2.build.+\")\n    testImplementation(\"org.junit.jupiter:junit-jupiter:6.1.3\")\n    testImplementation(\"org.mockbukkit.mockbukkit:mockbukkit-v26.2:4.116.1\")\n    testRuntimeOnly(\"org.junit.platform:junit-platform-launcher\")\n}\n\ntasks.test {\n    useJUnitPlatform()\n}\n</code></pre>\n<h3>Setup / teardown pattern</h3>\n<pre><code>import org.mockbukkit.mockbukkit.MockBukkit;\nimport org.mockbukkit.mockbukkit.ServerMock;\nimport org.mockbukkit.mockbukkit.entity.PlayerMock;\nimport org.junit.jupiter.api.*;\n\nclass MyPluginTest {\n\n    private static ServerMock server;\n    private static MyPlugin plugin;\n\n    @BeforeAll\n    static void setUp() {\n        // Start mock Bukkit server and load your plugin\n        server = MockBukkit.mock();\n        plugin = MockBukkit.load(MyPlugin.class);\n    }\n\n    @AfterAll\n    static void tearDown() {\n        MockBukkit.unmock();\n    }\n}\n</code></pre>\n<h3>Testing events</h3>\n<pre><code>@Test\nvoid playerJoin_getsWelcomeMessage() {\n    PlayerMock player = server.addPlayer(\"Steve\");\n    player.simulateJoin(); // fires PlayerJoinEvent\n\n    // Assert the player received the expected message component\n    player.assertSaid(\"Welcome, Steve!\");\n    // Or for Adventure components:\n    assertTrue(player.nextMessage().contains(\"Welcome\"));\n}\n\n@Test\nvoid onBlockBreak_cancelledForNonOp() {\n    PlayerMock player = server.addPlayer();\n    player.setOp(false);\n\n    Block block = player.getWorld().getBlockAt(0, 64, 0);\n    block.setType(Material.STONE);\n    BlockBreakEvent event = new BlockBreakEvent(block, player);\n    server.getPluginManager().callEvent(event);\n\n    assertTrue(event.isCancelled(), \"Non-op should not be able to break blocks\");\n}\n</code></pre>\n<h3>Testing commands</h3>\n<pre><code>@Test\nvoid mypluginInfo_returnsVersion() {\n    PlayerMock player = server.addPlayer(\"Admin\");\n    player.setOp(true);\n\n    boolean result = server.dispatchCommand(player, \"myplugin info\");\n\n    assertTrue(result);\n    player.assertSaid(\"Version: \" + plugin.getDescription().getVersion());\n}\n\n@Test\nvoid mypluginReload_requiresOp() {\n    PlayerMock player = server.addPlayer(\"NonOp\");\n    player.setOp(false);\n\n    server.dispatchCommand(player, \"myplugin reload\");\n\n    player.assertSaid(\"No permission.\");\n}\n</code></pre>\n<h3>Testing inventory / items</h3>\n<pre><code>@Test\nvoid giveKitCommand_givesPlayerItems() {\n    PlayerMock player = server.addPlayer();\n    \n    server.dispatchCommand(player, \"kit starter\");\n    \n    // Check inventory\n    assertTrue(player.getInventory().contains(Material.STONE_SWORD));\n    assertTrue(player.getInventory().contains(Material.BREAD, 16));\n}\n</code></pre>\n<h3>Testing scheduler tasks</h3>\n<pre><code>@Test\nvoid repeatingTask_firesAfterDelay() {\n    PlayerMock player = server.addPlayer();\n    \n    // Execute 40 ticks worth of scheduled tasks\n    server.getScheduler().performTicks(40L);\n    \n    // Assert expected side effect happened\n    assertEquals(2, plugin.getTaskCount());\n}\n</code></pre>\n<h3>Testing Folia-safe scheduler abstractions</h3>\n<p>MockBukkit does not emulate Folia's region-threaded runtime. The safe pattern is to\nwrap scheduling behind your own interface and unit test the abstraction boundary.</p>\n<pre><code>interface SchedulerFacade {\n    void runPlayerTask(Player player, Runnable task);\n    void runAsync(Runnable task);\n}\n\n@Test\nvoid playerTask_delegatesThroughFacade() {\n    List&lt;String&gt; calls = new ArrayList&lt;&gt;();\n    SchedulerFacade facade = new SchedulerFacade() {\n        @Override\n        public void runPlayerTask(Player player, Runnable task) {\n            calls.add(\"player\");\n            task.run();\n        }\n\n        @Override\n        public void runAsync(Runnable task) {\n            calls.add(\"async\");\n            task.run();\n        }\n    };\n\n    facade.runPlayerTask(server.addPlayer(), () -&gt; calls.add(\"ran\"));\n    assertEquals(List.of(\"player\", \"ran\"), calls);\n}\n</code></pre>\n<h3>Testing PDC</h3>\n<pre><code>import java.util.ArrayList;\nimport org.bukkit.damage.DamageSource;\nimport org.bukkit.damage.DamageType;\nimport org.bukkit.entity.LivingEntity;\n\n@Test\nvoid pdcKillCount_incrementsOnKill() {\n    PlayerMock player = server.addPlayer();\n    NamespacedKey key = new NamespacedKey(plugin, \"kills\");\n\n    // EntityDeathEvent requires a living victim and an explicit damage source.\n    LivingEntity victim = (LivingEntity) server.addMockEntity(EntityType.ZOMBIE);\n    DamageSource damageSource = DamageSource.builder(DamageType.GENERIC)\n        .withCausingEntity(player)\n        .withDirectEntity(player)\n        .build();\n    EntityDeathEvent deathEvent = new EntityDeathEvent(\n        victim, damageSource, new ArrayList&lt;&gt;(), 0\n    );\n    server.getPluginManager().callEvent(deathEvent);\n\n    int kills = player.getPersistentDataContainer()\n        .getOrDefault(key, PersistentDataType.INTEGER, 0);\n    assertEquals(1, kills);\n}\n</code></pre>\n<p>This dispatches a synthetic death event. For player attribution, the listener\nunder test should read <code>event.getDamageSource().getCausingEntity()</code>; test actual\ncombat attribution separately on a real server.</p>\n<h3>Testing item or chunk PDC writes</h3>\n<pre><code>@Test\nvoid itemPdc_roundTripsCustomId() {\n    NamespacedKey key = new NamespacedKey(plugin, \"custom_id\");\n    ItemStack item = new ItemStack(Material.STICK);\n\n    item.editMeta(meta -&gt; meta.getPersistentDataContainer().set(\n        key, PersistentDataType.STRING, \"wand\"\n    ));\n\n    String value = item.getItemMeta().getPersistentDataContainer()\n        .get(key, PersistentDataType.STRING);\n    assertEquals(\"wand\", value);\n}\n</code></pre>\n<hr>\n<h2>Current NeoForge Game Tests (26.x)</h2>\n<p>NeoForge 1.21.5 and later uses data-driven test environments and test instances,\nnot the old <code>@GameTestHolder</code> method-registration API. Store resources under\n<code>data/&lt;namespace&gt;/test_environment/</code> and <code>data/&lt;namespace&gt;/test_instance/</code>.\nA <code>test_instance</code> selects its environment, structure, timing, and either a\nregistered function or a block-based test.</p>\n<pre><code>{\n  \"environment\": \"minecraft:default\",\n  \"structure\": \"examplemod:example_structure\",\n  \"max_ticks\": 200,\n  \"setup_ticks\": 0,\n  \"required\": true,\n  \"type\": \"minecraft:function\",\n  \"function\": \"examplemod:example_function\"\n}\n</code></pre>\n<p>Register the <code>Consumer&lt;GameTestHelper&gt;</code> with a <code>DeferredRegister</code> for the\ncurrent <code>BuiltInRegistries.TEST_FUNCTION</code> registry, then attach that register to\nthe mod event bus. The function below makes the JSON reference above usable.\nUse <code>RegisterGameTestsEvent</code> only when registering environments and test\ninstances in code instead of data files. Keep the referenced structure in\n<code>data/&lt;namespace&gt;/structure/&lt;path&gt;.nbt</code> and mark success explicitly.</p>\n<pre><code>import java.util.function.Consumer;\nimport net.minecraft.core.registries.BuiltInRegistries;\nimport net.minecraft.gametest.framework.GameTestHelper;\nimport net.minecraft.world.level.block.Blocks;\nimport net.neoforged.bus.api.IEventBus;\nimport net.neoforged.fml.common.Mod;\nimport net.neoforged.neoforge.registries.DeferredHolder;\nimport net.neoforged.neoforge.registries.DeferredRegister;\n\n@Mod(ExampleGameTests.MOD_ID)\npublic final class ExampleGameTests {\n    public static final String MOD_ID = \"examplemod\";\n    private static final DeferredRegister&lt;Consumer&lt;GameTestHelper&gt;&gt; TEST_FUNCTIONS =\n        DeferredRegister.create(BuiltInRegistries.TEST_FUNCTION, MOD_ID);\n    public static final DeferredHolder&lt;Consumer&lt;GameTestHelper&gt;, Consumer&lt;GameTestHelper&gt;&gt;\n        EXAMPLE_FUNCTION = TEST_FUNCTIONS.register(\n            \"example_function\", () -&gt; ExampleGameTests::exampleTest\n        );\n\n    public ExampleGameTests(IEventBus modBus) {\n        TEST_FUNCTIONS.register(modBus);\n    }\n\n    public static void exampleTest(GameTestHelper helper) {\n        helper.assertBlockPresent(Blocks.AIR, 0, 0, 0);\n        helper.succeed();\n    }\n}\n</code></pre>\n<p>Run <code>./gradlew runGameTestServer</code>; the server exits with the count of required\nfailed tests. This command is for a real project, not this skills repository.</p>\n<hr>\n<h2>Current Fabric Game Tests (26.x)</h2>\n<p>Use Fabric Loom's dedicated Game Test source set. Configure it in the existing\n<code>fabricApi</code> block and keep its metadata and code under <code>src/gametest</code>, separate\nfrom ordinary unit tests.</p>\n<pre><code>fabricApi {\n    configureTests {\n        createSourceSet = true\n        modId = \"example-mod-test-${project.name}\"\n        enableGameTests = true\n        enableClientGameTests = true\n        eula = true\n    }\n}\n</code></pre>\n<p>Place <code>fabric.mod.json</code> in <code>src/gametest/resources/</code> and register server tests\nunder <code>fabric-gametest</code>; use <code>fabric-client-gametest</code> for client tests. Implement\nserver methods with Fabric's <code>net.fabricmc.fabric.api.gametest.v1.GameTest</code> and,\nwhen setup is needed before a method runs, <code>CustomTestMethodInvoker</code>.</p>\n<pre><code>package com.example.mymod;\n\nimport java.lang.reflect.Method;\nimport net.fabricmc.fabric.api.gametest.v1.CustomTestMethodInvoker;\nimport net.fabricmc.fabric.api.gametest.v1.GameTest;\nimport net.minecraft.gametest.framework.GameTestHelper;\nimport net.minecraft.world.level.block.Blocks;\n\npublic final class ExampleGameTest implements CustomTestMethodInvoker {\n    @GameTest\n    public void testBlock(GameTestHelper context) {\n        context.assertBlockPresent(Blocks.AIR, 0, 0, 0);\n        context.succeed();\n    }\n\n    @Override\n    public void invokeTestMethod(GameTestHelper context, Method method)\n            throws ReflectiveOperationException {\n        method.invoke(this, context);\n    }\n}\n</code></pre>\n<h3><code>src/gametest/resources/fabric.mod.json</code></h3>\n<pre><code>{\n  \"entrypoints\": {\n    \"fabric-gametest\": [\n      \"com.example.mymod.ExampleGameTest\"\n    ]\n  }\n}\n</code></pre>\n<p>Keep the <code>fabric-gametest</code> entrypoint in sync with the concrete Game Test class.\nFabric's server Game Tests run with <code>build</code>; use <code>runClientGameTest</code> for client\ntests. Follow the current Fabric documentation for project-specific Loom options\nand headless client CI.</p>\n<hr>\n<h2>Legacy NeoForge Game Tests (1.21.3 only)</h2>\n<p>Keep annotation-based tests isolated to an explicit 1.21.3 lane. The class can\nbe registered by either <code>@GameTestHolder(MOD_ID)</code> or a\n<code>RegisterGameTestsEvent</code> listener. Do not register a <code>@GameTestHolder</code> class\nagain with <code>modEventBus.register(MyGameTests.class)</code>.</p>\n<pre><code>import net.minecraft.gametest.framework.GameTest;\nimport net.minecraft.gametest.framework.GameTestHelper;\nimport net.neoforged.neoforge.gametest.GameTestHolder;\nimport net.neoforged.neoforge.gametest.PrefixGameTestTemplate;\n\n@GameTestHolder(\"examplemod\")\n@PrefixGameTestTemplate(false)\npublic final class ExampleGameTests {\n    @GameTest(template = \"example_structure\")\n    public static void smoke(GameTestHelper helper) {\n        helper.succeed();\n    }\n}\n</code></pre>\n<p>For <code>RegisterGameTestsEvent</code>, register the class on the mod event bus and set\n<code>templateNamespace = MOD_ID</code> on each <code>@GameTest</code>. Legacy templates are <code>.nbt</code>\nfiles under <code>data/&lt;namespace&gt;/structure/</code>; <code>@PrefixGameTestTemplate(false)</code>\ncontrols whether the class name is added to the template path. When <code>template</code>\nis omitted, the path uses the lowercase method name and, unless that prefix is\ndisabled, the lowercase simple class name followed by a dot. <code>template</code> is the\npath name only; configure its namespace through <code>templateNamespace</code> or\n<code>@GameTestHolder</code>.</p>\n<hr>\n<h2>CI</h2>\n<p>Keep fast unit/mock tests separate from a loader's Game Test task, and select\nthe Java version by lane: 25 for 26.x, 21 for 1.21.x, and 17 for Forge 1.20.1.\nUpload test reports when a runtime-facing job fails. Do not assume a task name\nfrom another loader: Fabric server Game Tests run with <code>build</code>, while NeoForge\nuses <code>runGameTestServer</code>. MockBukkit does not prove Folia thread safety or real\nserver bootstrap.</p>\n<hr>\n<h2>References</h2>\n<ul>\n<li>MockBukkit GitHub: <a href=\"https://github.com/MockBukkit/MockBukkit\">https://github.com/MockBukkit/MockBukkit</a></li>\n<li>MockBukkit docs: <a href=\"https://docs.mockbukkit.org/\">https://docs.mockbukkit.org/</a></li>\n<li>Fabric automated testing: <a href=\"https://docs.fabricmc.net/develop/automatic-testing\">https://docs.fabricmc.net/develop/automatic-testing</a></li>\n<li>NeoForge 26.x Game Tests: <a href=\"https://docs.neoforged.net/docs/misc/gametest/\">https://docs.neoforged.net/docs/misc/gametest/</a></li>\n<li>NeoForge 1.21.3 Game Tests: <a href=\"https://docs.neoforged.net/docs/1.21.3/misc/gametest/\">https://docs.neoforged.net/docs/1.21.3/misc/gametest/</a></li>\n<li>JUnit 5 user guide: <a href=\"https://junit.org/junit5/docs/current/user-guide/\">https://junit.org/junit5/docs/current/user-guide/</a></li>\n</ul>\n","files":[{"path":"references/test-layouts.md","sizeBytes":3068,"isText":true},{"path":"scripts/validate-test-layout.sh","sizeBytes":15285,"isText":true},{"path":"SKILL.md","sizeBytes":15327,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-16T16:00:19.742826Z","sha256":"403B04FE7698F219AE4BCC3048E536B915BF4B98ED141FAD255351BD6B45645A","sizeBytes":10954},"review":null,"source":{"repositoryUrl":"https://github.com/Jahrome907/minecraft-agent-skills","path":".codex/skills/minecraft-testing","license":"MIT","commit":"dd57c5a97741cdc0eb5bb3a8f876581a4f09eeb0","subtreeSha":"88081893CC028F0D2BF6D4BA905A4110874089E0ECC3ECE4433841D43BE404AE","lastSyncedAt":"2026-09-25T06:49:17.478439Z"},"reviewedAt":"2026-09-16T16:01:54.24743Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/Jahrome907/minecraft-agent-skills/tree/main/.codex/skills/minecraft-testing"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install jahrome907-minecraft-agent-skills@llmmart"},{"target":"git","command":"git clone https://github.com/Jahrome907/minecraft-agent-skills.git"}]}