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 tasks fire correctly
Testing that custom events are called and cancellable
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.
Version Notes
MockBukkit v4 (artifact id mockbukkit-v4) targets Paper 1.21. Earlier versions (mockbukkit-v1.x) target legacy Bukkit/Spigot and are not compatible.
MockBukkit 4.x requires JUnit 5 (junit-jupiter). JUnit 4 annotations (@Before, @After) do not work.
1---2name: testing3description: Testing Skill — Paper4---5# Testing Skill — Paper67## Purpose8Reference 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.910## When to Use This Skill11- Testing logic in event handlers, commands, or service classes12- Verifying `ItemMeta`, inventory contents, or PDC values in unit tests13- Running the Bukkit scheduler in tests to confirm delayed/repeating tasks fire correctly14- Testing that custom events are called and cancellable1516## API Quick Reference1718| Class / Method | Purpose | Notes |19|---------------|---------|-------|20| `MockBukkit.mock()` | Start the mock server | Returns `ServerMock`; call in `@BeforeEach` |21| `MockBukkit.unmock()` | Shut down and clean up | Call in `@AfterEach`; mandatory |22| `MockBukkit.load(MyPlugin.class)` | Load plugin under test | Returns plugin instance |23| `ServerMock` | Simulated `Server` | Returned by `MockBukkit.mock()` |24| `server.addPlayer()` | Add a simulated player | Returns `PlayerMock` |25| `server.addPlayer(String)` | Add player with specific name | |26| `PlayerMock` | Simulated `Player` | Extends `CraftPlayer`-compatible mock |27| `player.assertSaid(String)` | Assert chat message received | Exact string match |28| `player.assertNoMoreSaid()` | Assert no more messages | |29| `server.getScheduler().performTicks(long)` | Advance scheduler by N ticks | Triggers `runTaskLater`/`runTaskTimer` |30| `server.getScheduler().performOneTick()` | Advance by 1 tick | |31| `MockBukkit.createMockPlugin()` | Create a no-op plugin | For registering listeners under test |3233## Code Pattern3435```java36package com.yourorg.myplugin;3738import be.seeseemelk.mockbukkit.MockBukkit;39import be.seeseemelk.mockbukkit.ServerMock;40import be.seeseemelk.mockbukkit.entity.PlayerMock;41import org.junit.jupiter.api.AfterEach;42import org.junit.jupiter.api.BeforeEach;43import org.junit.jupiter.api.Test;4445import static org.junit.jupiter.api.Assertions.*;4647class MyPluginTest {4849 private ServerMock server;50 private MyPlugin plugin;5152 @BeforeEach53 void setUp() {54 server = MockBukkit.mock();55 plugin = MockBukkit.load(MyPlugin.class);56 }5758 @AfterEach59 void tearDown() {60 MockBukkit.unmock();61 }6263 @Test64 void playerReceivesWelcomeMessage() {65 PlayerMock player = server.addPlayer();66 // Simulate the PlayerJoinEvent being triggered67 player.simulateLogin();6869 player.assertSaid("Welcome, " + player.getName() + "!");70 player.assertNoMoreSaid();71 }7273 @Test74 void scheduledTaskRunsAfterDelay() {75 PlayerMock player = server.addPlayer();76 plugin.scheduleReminder(player);7778 // Fast-forward 5 seconds (100 ticks)79 server.getScheduler().performTicks(100L);8081 player.assertSaid("Don't forget to vote!");82 }83}84```8586## Common Pitfalls8788- **Not calling `MockBukkit.unmock()` in `@AfterEach`**: The mock server holds static state. Forgetting `unmock()` causes subsequent test classes to fail with "already mocked" errors.8990- **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.9192- **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.9394- **`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()`.9596- **World not available by default**: `server.addSimpleWorld("world")` creates a world. Calling `Bukkit.getWorld("world")` returns `null` unless added first.9798## Version Notes99100- **MockBukkit v4** (artifact id `mockbukkit-v4`) targets Paper 1.21. Earlier versions (`mockbukkit-v1.x`) target legacy Bukkit/Spigot and are not compatible.101- MockBukkit 4.x requires **JUnit 5** (`junit-jupiter`). JUnit 4 annotations (`@Before`, `@After`) do not work.102103## Related Skills104105- [mockbukkit-patterns.md](mockbukkit-patterns.md) — Advanced patterns: config mocking, world mocking, event calling, command dispatch106- [../scheduling/scheduler-tasks.md](../scheduling/scheduler-tasks.md) — `performTicks()` maps to `runTaskTimer` tick counts107- [../events/custom-events.md](../events/custom-events.md) — Calling and testing custom events
Run npx skillmds@latest add mrpippi/testing in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Testing Skill — Paper It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
MrPippi (@mrpippi) published this skill. Their other Agent Skills are listed on their SkillMD profile.