docs/paper/testing/SKILL.md
# Testing Skill — Paper ## Purpose Reference this skill when writing unit tests for Paper plugin code. Covers MockBukkit 4.x for JUnit 5: server mock lifecycle, player mocks, scheduler tick simulation, and testing event handlers and commands without a real server. ## When to Use This Skill - Testing logic in event handlers, commands, or service classes - Verifying `ItemMeta`, inventory contents, or PDC values in unit tests - Running the Bukkit scheduler in tests to confirm delayed/repeating ta
npx skillsauth add MrPippi/MPS docs/paper/testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference this skill when writing unit tests for Paper plugin code. Covers MockBukkit 4.x for JUnit 5: server mock lifecycle, player mocks, scheduler tick simulation, and testing event handlers and commands without a real server.
ItemMeta, inventory contents, or PDC values in unit tests| Class / Method | Purpose | Notes |
|---------------|---------|-------|
| MockBukkit.mock() | Start the mock server | Returns ServerMock; call in @BeforeEach |
| MockBukkit.unmock() | Shut down and clean up | Call in @AfterEach; mandatory |
| MockBukkit.load(MyPlugin.class) | Load plugin under test | Returns plugin instance |
| ServerMock | Simulated Server | Returned by MockBukkit.mock() |
| server.addPlayer() | Add a simulated player | Returns PlayerMock |
| server.addPlayer(String) | Add player with specific name | |
| PlayerMock | Simulated Player | Extends CraftPlayer-compatible mock |
| player.assertSaid(String) | Assert chat message received | Exact string match |
| player.assertNoMoreSaid() | Assert no more messages | |
| server.getScheduler().performTicks(long) | Advance scheduler by N ticks | Triggers runTaskLater/runTaskTimer |
| server.getScheduler().performOneTick() | Advance by 1 tick | |
| MockBukkit.createMockPlugin() | Create a no-op plugin | For registering listeners under test |
package com.yourorg.myplugin;
import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import be.seeseemelk.mockbukkit.entity.PlayerMock;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MyPluginTest {
private ServerMock server;
private MyPlugin plugin;
@BeforeEach
void setUp() {
server = MockBukkit.mock();
plugin = MockBukkit.load(MyPlugin.class);
}
@AfterEach
void tearDown() {
MockBukkit.unmock();
}
@Test
void playerReceivesWelcomeMessage() {
PlayerMock player = server.addPlayer();
// Simulate the PlayerJoinEvent being triggered
player.simulateLogin();
player.assertSaid("Welcome, " + player.getName() + "!");
player.assertNoMoreSaid();
}
@Test
void scheduledTaskRunsAfterDelay() {
PlayerMock player = server.addPlayer();
plugin.scheduleReminder(player);
// Fast-forward 5 seconds (100 ticks)
server.getScheduler().performTicks(100L);
player.assertSaid("Don't forget to vote!");
}
}
Not calling MockBukkit.unmock() in @AfterEach: The mock server holds static state. Forgetting unmock() causes subsequent test classes to fail with "already mocked" errors.
Using Bukkit.getServer() in non-mock code paths: Code that calls Bukkit.getServer() statically will return the ServerMock during tests, which is correct — but only after MockBukkit.mock() has been called.
Testing async tasks with performTicks(): runTaskAsynchronously tasks are NOT executed by performTicks() — only synchronous main-thread tasks are. Refactor async code so the IO callback switches back to the main thread (testable part) and the IO layer is a separate mock.
MockBukkit.load() triggers onEnable(): Your plugin's onEnable() runs during load(). If onEnable() depends on external services (e.g., Vault, a database), mock those before calling load().
World not available by default: server.addSimpleWorld("world") creates a world. Calling Bukkit.getWorld("world") returns null unless added first.
mockbukkit-v4) targets Paper 1.21. Earlier versions (mockbukkit-v1.x) target legacy Bukkit/Spigot and are not compatible.junit-jupiter). JUnit 4 annotations (@Before, @After) do not work.performTicks() maps to runTaskTimer tick countsdevelopment
透過 NMS Scoreboard/Objective/Team API 操作 sidebar、tablist 顯示名稱與計分板(Paper NMS + Mojang-mapped)/ Operate sidebar, tablist, and scoreboard via NMS Scoreboard/Objective/Team API
research
操作 GameProfile 進行 skin 注入,用於 NPC 外觀設定與假玩家實體(Paper NMS + Mojang-mapped)/ Manipulate GameProfile for skin injection used in NPC appearance and fake player entities
tools
透過 ClientboundLevelParticlesPacket 實現進階 NMS 粒子效果:客戶端專屬、大量粒子、自定義參數(Paper NMS + Mojang-mapped)/ Advanced NMS particle effects via ClientboundLevelParticlesPacket with per-client and bulk support
documentation
直接操作 CompoundTag 讀寫物品、實體、方塊實體的 NBT 資料(Paper NMS + Mojang-mapped)/ Read and write NBT data on items, entities, and block entities via CompoundTag