MCPInstall

minecraft-testing

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.

Installs
262
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/jahrome907/minecraft-agent-skills --skill minecraft-testing
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads minecraft-testing automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Minecraft Testing Skill

Testing Strategies Overview

ApproachBest ForRequires Game?
JUnit Jupiter (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)

Use Java 25 for current 26.x projects. Keep legacy 1.21.x examples on Java 21 and Forge 1.20.1 on Java 17. Do not combine source layouts or APIs across versions.

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).

Bundled References And Helpers

  • Layout guide: references/test-layouts.md
  • Fixture/layout validator: ./scripts/validate-test-layout.sh --root <project>

Use the validator before copying a test layout into a real project. It checks visible static dependencies, metadata, and literal structure references. It does not compile the project or prove a Game Test can run.


Unit Testing (JUnit Jupiter — No Minecraft)

JUnit Platform task

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

For Fabric code that needs loader setup, use Fabric Loader JUnit rather than assuming ordinary JUnit initialized Minecraft:

dependencies {
    testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}"
}

test {
    useJUnitPlatform()
}

When a unit test reaches registry-dependent Minecraft classes, initialize only the required bootstrap in test setup. The current Fabric guide uses SharedConstants.tryDetectVersion() and Bootstrap.bootStrap() for that case.

Example pure unit test

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

class CooldownManagerTest {

    @Test
    void cooldown_tracksPlayersIndependently() {
        var manager = new CooldownManager(500L); // 500ms cooldown
        manager.startCooldown("steve");
        assertTrue(manager.isOnCooldown("steve"));
        assertFalse(manager.isOnCooldown("notExisting"));
    }

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

MockBukkit (Paper/Bukkit Plugin Tests)

The paired dependencies below are a retained 26.2 example. For 26.3, verify a published MockBukkit artifact for that exact Paper line before changing either dependency; a 26.2 mock does not establish 26.3 server behavior.

build.gradle.kts

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

dependencies {
    compileOnly("io.papermc.paper:paper-api:26.2.build.+")
    testImplementation("org.junit.jupiter:junit-jupiter:6.1.3")
    testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v26.2:4.116.1")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.test {
    useJUnitPlatform()
}

Setup / teardown pattern

import org.mockbukkit.mockbukkit.MockBukkit;
import org.mockbukkit.mockbukkit.ServerMock;
import org.mockbukkit.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();
    }
}

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 Folia-safe scheduler abstractions

MockBukkit does not emulate Folia's region-threaded runtime. The safe pattern is to wrap scheduling behind your own interface and unit test the abstraction boundary.

interface SchedulerFacade {
    void runPlayerTask(Player player, Runnable task);
    void runAsync(Runnable task);
}

@Test
void playerTask_delegatesThroughFacade() {
    List<String> calls = new ArrayList<>();
    SchedulerFacade facade = new SchedulerFacade() {
        @Override
        public void runPlayerTask(Player player, Runnable task) {
            calls.add("player");
            task.run();
        }

        @Override
        public void runAsync(Runnable task) {
            calls.add("async");
            task.run();
        }
    };

    facade.runPlayerTask(server.addPlayer(), () -> calls.add("ran"));
    assertEquals(List.of("player", "ran"), calls);
}

Testing PDC

import java.util.ArrayList;
import org.bukkit.damage.DamageSource;
import org.bukkit.damage.DamageType;
import org.bukkit.entity.LivingEntity;

@Test
void pdcKillCount_incrementsOnKill() {
    PlayerMock player = server.addPlayer();
    NamespacedKey key = new NamespacedKey(plugin, "kills");

    // EntityDeathEvent requires a living victim and an explicit damage source.
    LivingEntity victim = (LivingEntity) server.addMockEntity(EntityType.ZOMBIE);
    DamageSource damageSource = DamageSource.builder(DamageType.GENERIC)
        .withCausingEntity(player)
        .withDirectEntity(player)
        .build();
    EntityDeathEvent deathEvent = new EntityDeathEvent(
        victim, damageSource, new ArrayList<>(), 0
    );
    server.getPluginManager().callEvent(deathEvent);

    int kills = player.getPersistentDataContainer()
        .getOrDefault(key, PersistentDataType.INTEGER, 0);
    assertEquals(1, kills);
}

This dispatches a synthetic death event. For player attribution, the listener under test should read event.getDamageSource().getCausingEntity(); test actual combat attribution separately on a real server.

Testing item or chunk PDC writes

@Test
void itemPdc_roundTripsCustomId() {
    NamespacedKey key = new NamespacedKey(plugin, "custom_id");
    ItemStack item = new ItemStack(Material.STICK);

    item.editMeta(meta -> meta.getPersistentDataContainer().set(
        key, PersistentDataType.STRING, "wand"
    ));

    String value = item.getItemMeta().getPersistentDataContainer()
        .get(key, PersistentDataType.STRING);
    assertEquals("wand", value);
}

Current NeoForge Game Tests (26.x)

NeoForge 1.21.5 and later uses data-driven test environments and test instances, not the old @GameTestHolder method-registration API. Store resources under data/<namespace>/test_environment/ and data/<namespace>/test_instance/. A test_instance selects its environment, structure, timing, and either a registered function or a block-based test.

{
  "environment": "minecraft:default",
  "structure": "examplemod:example_structure",
  "max_ticks": 200,
  "setup_ticks": 0,
  "required": true,
  "type": "minecraft:function",
  "function": "examplemod:example_function"
}

Register the Consumer<GameTestHelper> with a DeferredRegister for the current BuiltInRegistries.TEST_FUNCTION registry, then attach that register to the mod event bus. The function below makes the JSON reference above usable. Use RegisterGameTestsEvent only when registering environments and test instances in code instead of data files. Keep the referenced structure in data/<namespace>/structure/<path>.nbt and mark success explicitly.

import java.util.function.Consumer;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.level.block.Blocks;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;

@Mod(ExampleGameTests.MOD_ID)
public final class ExampleGameTests {
    public static final String MOD_ID = "examplemod";
    private static final DeferredRegister<Consumer<GameTestHelper>> TEST_FUNCTIONS =
        DeferredRegister.create(BuiltInRegistries.TEST_FUNCTION, MOD_ID);
    public static final DeferredHolder<Consumer<GameTestHelper>, Consumer<GameTestHelper>>
        EXAMPLE_FUNCTION = TEST_FUNCTIONS.register(
            "example_function", () -> ExampleGameTests::exampleTest
        );

    public ExampleGameTests(IEventBus modBus) {
        TEST_FUNCTIONS.register(modBus);
    }

    public static void exampleTest(GameTestHelper helper) {
        helper.assertBlockPresent(Blocks.AIR, 0, 0, 0);
        helper.succeed();
    }
}

Run ./gradlew runGameTestServer; the server exits with the count of required failed tests. This command is for a real project, not this skills repository.


Current Fabric Game Tests (26.x)

Use Fabric Loom's dedicated Game Test source set. Configure it in the existing fabricApi block and keep its metadata and code under src/gametest, separate from ordinary unit tests.

fabricApi {
    configureTests {
        createSourceSet = true
        modId = "example-mod-test-${project.name}"
        enableGameTests = true
        enableClientGameTests = true
        eula = true
    }
}

Place fabric.mod.json in src/gametest/resources/ and register server tests under fabric-gametest; use fabric-client-gametest for client tests. Implement server methods with Fabric's net.fabricmc.fabric.api.gametest.v1.GameTest and, when setup is needed before a method runs, CustomTestMethodInvoker.

package com.example.mymod;

import java.lang.reflect.Method;
import net.fabricmc.fabric.api.gametest.v1.CustomTestMethodInvoker;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.level.block.Blocks;

public final class ExampleGameTest implements CustomTestMethodInvoker {
    @GameTest
    public void testBlock(GameTestHelper context) {
        context.assertBlockPresent(Blocks.AIR, 0, 0, 0);
        context.succeed();
    }

    @Override
    public void invokeTestMethod(GameTestHelper context, Method method)
            throws ReflectiveOperationException {
        method.invoke(this, context);
    }
}

src/gametest/resources/fabric.mod.json

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

Keep the fabric-gametest entrypoint in sync with the concrete Game Test class. Fabric's server Game Tests run with build; use runClientGameTest for client tests. Follow the current Fabric documentation for project-specific Loom options and headless client CI.


Legacy NeoForge Game Tests (1.21.3 only)

Keep annotation-based tests isolated to Minecraft 1.21.3. The class can be registered by either @GameTestHolder(MOD_ID) or a RegisterGameTestsEvent listener. Do not register a @GameTestHolder class again with modEventBus.register(MyGameTests.class).

import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.neoforged.neoforge.gametest.GameTestHolder;
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;

@GameTestHolder("examplemod")
@PrefixGameTestTemplate(false)
public final class ExampleGameTests {
    @GameTest(template = "example_structure")
    public static void smoke(GameTestHelper helper) {
        helper.succeed();
    }
}

For RegisterGameTestsEvent, register the class on the mod event bus and set templateNamespace = MOD_ID on each @GameTest. Legacy templates are .nbt files under data/<namespace>/structure/; @PrefixGameTestTemplate(false) controls whether the class name is added to the template path. When template is omitted, the path uses the lowercase method name and, unless that prefix is disabled, the lowercase simple class name followed by a dot. template is the path name only; configure its namespace through templateNamespace or @GameTestHolder.


CI

Keep fast unit/mock tests separate from a loader's Game Test task, and select the Java version for each Minecraft version: 25 for 26.x, 21 for 1.21.x, and 17 for Forge 1.20.1. Upload test reports when a runtime-facing job fails. Do not assume a task name from another loader: Fabric server Game Tests run with build, while NeoForge uses runGameTestServer. MockBukkit does not prove Folia thread safety or real server bootstrap.


References

FAQ

What does the minecraft-testing skill do?

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.

How do I install minecraft-testing?

Run npx skills add https://github.com/jahrome907/minecraft-agent-skills --skill minecraft-testing in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/minecraft-testing/ (personal) or .claude/skills/minecraft-testing/ (project).

Which AI agents can use minecraft-testing?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is minecraft-testing safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains minecraft-testing?

It is published by jahrome907 in jahrome907/minecraft-agent-skills. It has 262 recorded installs.

More from jahrome907/minecraft-agent-skills

View all →

minecraft-modding

jahrome907/minecraft-agent-skills

Create, modify, debug, or migrate Minecraft mods for current NeoForge or Fabric 26.x, legacy 1.21.x, and Forge 1.20.1. Use for loader-based gameplay code and assets; use minecraft-multiloader when one codebase must target both modern loaders.

minecraft-resource-pack

jahrome907/minecraft-agent-skills

Create and debug Minecraft 26.x and 1.21.x resource packs, including pack metadata, textures, models, blockstates, item definitions, sounds, fonts, animations, and shaders. Use for client-side visual or audio assets without gameplay code.

AI & Agents241 installs

minecraft-datapack

jahrome907/minecraft-agent-skills

Create, edit, and debug vanilla Minecraft 26.x and 1.21.x datapacks, including functions, advancements, recipes, loot tables, predicates, tags, and pack metadata. Use when the deliverable is a datapack file tree without Java or loader APIs.

Backend & APIs200 installs

minecraft-server-admin

jahrome907/minecraft-agent-skills

Set up, operate, tune, and troubleshoot Minecraft Java 26.x and legacy 1.21.x servers across Paper, Purpur, Folia, Velocity, Fabric, and NeoForge. Use for infrastructure, backups, proxies, and live operations, not plugin or mod development.

Backend & APIs184 installs

minecraft-plugin-dev

jahrome907/minecraft-agent-skills

Create, modify, and debug server plugins for current Paper 26.x on Java 25 or legacy Bukkit-derived 1.21.x servers on Java 21. Use for JavaPlugin APIs, events, commands, schedulers, configuration, PDC, and Adventure, not client mods or vanilla datapacks.

Backend & APIs176 installs

minecraft-world-generation

jahrome907/minecraft-agent-skills

Create and debug Minecraft 26.x and legacy 1.21.x world generation for datapacks, NeoForge, or Fabric, including biomes, dimensions, features, structures, and biome modifiers. Use for worldgen data or registration, not general gameplay systems.

AI & Agents168 installs

Popular testing & qa skills

View category →

tdd

mattpocock/skills

Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.

Testing & QA1.2M installs

grilling

mattpocock/skills

Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.

Testing & QA996.2K installs

migrate-to-shoehorn

mattpocock/skills

Migrate test files from as type assertions to @total-typescript/shoehorn. Use when user mentions shoehorn, wants to replace as in tests, or needs partial test data.

Testing & QA484.1K installs

run-train

lllllllama/rigorpilot-skills

Rigor Train skill for deep learning research repositories. Use when a documented or selected training command should be run conservatively for startup verification, short-run verification, full kickoff, or resume, with command, config, seed, log, checkpoint, status, and metric evidence written to standardized trainoutputs/. Do not use for environment setup, exploratory sweeps, speculative idea implementation, or end-to-end orchestration.

Testing & QA337.8K installs

test-driven-development

obra/superpowers

Use when implementing any feature or bugfix, before writing implementation code

Testing & QA261.8K installs

Use when implementation is complete, all tests pass, and you need to decide how to integrate the work

Testing & QA216.8K installs