> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/PaperMC/Paper/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing Plugins

> Learn how to test Paper plugins effectively

Testing is crucial for ensuring your plugin works correctly. Paper includes a test-plugin module that demonstrates testing patterns and plugin features.

## Test Plugin Structure

Paper's test plugin is located at `test-plugin/` in the Paper repository and serves as both a testing tool and example implementation.

### Test Plugin Components

From the Paper test plugin source:

<CodeGroup>
  ```yaml paper-plugin.yml theme={null}
  name: Paper-Test-Plugin
  version: ${version}
  main: io.papermc.testplugin.TestPlugin
  description: Paper Test Plugin
  author: PaperMC
  api-version: ${apiversion}
  load: STARTUP
  bootstrapper: io.papermc.testplugin.TestPluginBootstrap
  loader: io.papermc.testplugin.TestPluginLoader
  defaultPerm: FALSE
  permissions:
  dependencies:
  ```

  ```java TestPlugin.java theme={null}
  package io.papermc.testplugin;

  import org.bukkit.event.Listener;
  import org.bukkit.plugin.java.JavaPlugin;

  public final class TestPlugin extends JavaPlugin implements Listener {

      @Override
      public void onEnable() {
          this.getServer().getPluginManager().registerEvents(this, this);
          
          // Example: Register Brigadier commands
          // io.papermc.testplugin.brigtests.Registration.registerViaOnEnable(this);
      }
  }
  ```

  ```java TestPluginBootstrap.java theme={null}
  package io.papermc.testplugin;

  import io.papermc.paper.plugin.bootstrap.BootstrapContext;
  import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
  import org.jetbrains.annotations.NotNull;

  public class TestPluginBootstrap implements PluginBootstrap {

      @Override
      public void bootstrap(@NotNull BootstrapContext context) {
          // Example: Register commands during bootstrap
          // io.papermc.testplugin.brigtests.Registration.registerViaBootstrap(context);
      }
  }
  ```

  ```java TestPluginLoader.java theme={null}
  package io.papermc.testplugin;

  import io.papermc.paper.plugin.loader.PluginClasspathBuilder;
  import io.papermc.paper.plugin.loader.PluginLoader;
  import org.jetbrains.annotations.NotNull;

  public class TestPluginLoader implements PluginLoader {
      @Override
      public void classloader(@NotNull PluginClasspathBuilder classpathBuilder) {
          // Configure plugin classpath
      }
  }
  ```
</CodeGroup>

## Manual Testing

Manual testing involves running your plugin on a Paper server and testing functionality by hand.

<Steps>
  <Step title="Set up test server">
    Create a test server directory:

    ```bash theme={null}
    mkdir test-server
    cd test-server

    # Download Paper JAR
    wget https://api.papermc.io/v2/projects/paper/versions/1.21.11/builds/latest/downloads/paper-1.21.11-latest.jar

    # Accept EULA
    echo "eula=true" > eula.txt

    # Create plugins directory
    mkdir plugins
    ```
  </Step>

  <Step title="Build and deploy plugin">
    Build your plugin and copy it to the test server:

    <CodeGroup>
      ```bash Maven theme={null}
      mvn clean package
      cp target/MyPlugin-1.0.0.jar test-server/plugins/
      ```

      ```bash Gradle theme={null}
      ./gradlew build
      cp build/libs/MyPlugin-1.0.0.jar test-server/plugins/
      ```
    </CodeGroup>
  </Step>

  <Step title="Run the server">
    Start the test server:

    ```bash theme={null}
    java -jar paper-1.21.11-latest.jar --nogui
    ```

    Watch the console for:

    * Plugin loading messages
    * Any errors or warnings
    * Successful enable confirmation
  </Step>

  <Step title="Test functionality">
    Connect to the server and test:

    * Commands work correctly
    * Events fire as expected
    * Configuration loads properly
    * Permissions function correctly
  </Step>
</Steps>

## Automated Testing Setup

While Paper doesn't include a built-in testing framework, you can use standard Java testing tools.

### Add Testing Dependencies

<CodeGroup>
  ```xml Maven (pom.xml) theme={null}
  <dependencies>
      <!-- Paper API -->
      <dependency>
          <groupId>io.papermc.paper</groupId>
          <artifactId>paper-api</artifactId>
          <version>1.21.11-R0.1-SNAPSHOT</version>
          <scope>provided</scope>
      </dependency>
      
      <!-- JUnit for testing -->
      <dependency>
          <groupId>org.junit.jupiter</groupId>
          <artifactId>junit-jupiter</artifactId>
          <version>5.10.0</version>
          <scope>test</scope>
      </dependency>
      
      <!-- Mockito for mocking -->
      <dependency>
          <groupId>org.mockito</groupId>
          <artifactId>mockito-core</artifactId>
          <version>5.5.0</version>
          <scope>test</scope>
      </dependency>
  </dependencies>
  ```

  ```kotlin Gradle (build.gradle.kts) theme={null}
  dependencies {
      compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
      
      testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
      testImplementation("org.mockito:mockito-core:5.5.0")
  }

  tasks.test {
      useJUnitPlatform()
  }
  ```
</CodeGroup>

## Unit Testing

Test individual components in isolation:

```java theme={null}
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import org.bukkit.entity.Player;
import org.bukkit.command.CommandSender;
import io.papermc.paper.command.brigadier.CommandSourceStack;

class HelloCommandTest {
    
    private HelloCommand command;
    private CommandSourceStack stack;
    private CommandSender sender;
    
    @BeforeEach
    void setUp() {
        command = new HelloCommand();
        stack = mock(CommandSourceStack.class);
        sender = mock(CommandSender.class);
        
        when(stack.getSender()).thenReturn(sender);
    }
    
    @Test
    void testExecuteWithNoArgs() {
        command.execute(stack, new String[]{});
        
        verify(sender).sendMessage("Hello, world!");
    }
    
    @Test
    void testExecuteWithName() {
        command.execute(stack, new String[]{"Steve"});
        
        verify(sender).sendMessage("Hello, Steve!");
    }
    
    @Test
    void testPermission() {
        assertEquals("myplugin.hello", command.permission());
    }
}
```

## Testing Event Listeners

```java theme={null}
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.entity.Player;
import net.kyori.adventure.text.Component;

class PlayerListenerTest {
    
    private PlayerListener listener;
    private PlayerJoinEvent event;
    private Player player;
    
    @BeforeEach
    void setUp() {
        listener = new PlayerListener();
        event = mock(PlayerJoinEvent.class);
        player = mock(Player.class);
        
        when(event.getPlayer()).thenReturn(player);
    }
    
    @Test
    void testPlayerJoinSendsWelcomeMessage() {
        listener.onPlayerJoin(event);
        
        verify(player).sendMessage("Welcome to the server!");
    }
    
    @Test
    void testJoinMessageModified() {
        listener.onPlayerJoin(event);
        
        verify(event).joinMessage(any(Component.class));
    }
}
```

## Testing Configuration

```java theme={null}
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.StringReader;

class ConfigTest {
    
    @Test
    void testDefaultConfiguration() {
        String yaml = """
            enable-feature: true
            max-players: 100
            welcome-message: Welcome!
            """;
        
        YamlConfiguration config = YamlConfiguration.loadConfiguration(
            new StringReader(yaml));
        
        assertTrue(config.getBoolean("enable-feature"));
        assertEquals(100, config.getInt("max-players"));
        assertEquals("Welcome!", config.getString("welcome-message"));
    }
    
    @Test
    void testConfigWrapper() {
        JavaPlugin plugin = mock(JavaPlugin.class);
        FileConfiguration fileConfig = new YamlConfiguration();
        fileConfig.set("enable-feature", true);
        fileConfig.set("max-players", 100);
        
        when(plugin.getConfig()).thenReturn(fileConfig);
        
        Config config = new Config(plugin);
        
        assertTrue(config.isFeatureEnabled());
        assertEquals(100, config.getMaxPlayers());
    }
}
```

## Integration Testing

Test multiple components working together:

```java theme={null}
class PluginIntegrationTest {
    
    private JavaPlugin plugin;
    private Server server;
    
    @BeforeEach
    void setUp() {
        server = mock(Server.class);
        plugin = mock(JavaPlugin.class);
        
        when(plugin.getServer()).thenReturn(server);
        when(plugin.getDataFolder()).thenReturn(new File("test-data"));
    }
    
    @Test
    void testPluginInitialization() {
        // Test that plugin initializes correctly
        assertNotNull(plugin.getServer());
        assertNotNull(plugin.getDataFolder());
    }
}
```

## Testing Best Practices

### 1. Mock External Dependencies

```java theme={null}
Server server = mock(Server.class);
Player player = mock(Player.class);
PluginManager pluginManager = mock(PluginManager.class);

when(server.getPluginManager()).thenReturn(pluginManager);
when(player.getName()).thenReturn("TestPlayer");
```

### 2. Test Edge Cases

```java theme={null}
@Test
void testCommandWithEmptyArgs() {
    command.execute(stack, new String[]{});
    // Verify appropriate handling
}

@Test
void testCommandWithNullArgs() {
    assertThrows(NullPointerException.class, () -> {
        command.execute(stack, null);
    });
}

@Test
void testCommandWithInvalidArgs() {
    command.execute(stack, new String[]{"invalid"});
    verify(sender).sendMessage(contains("Invalid"));
}
```

### 3. Use Descriptive Test Names

```java theme={null}
@Test
void playerWithoutPermissionCannotUseAdminCommand() {
    when(sender.hasPermission("myplugin.admin")).thenReturn(false);
    
    assertFalse(command.canUse(sender));
}

@Test
void configReloadPreservesCustomValues() {
    config.set("custom-value", "test");
    config.reload();
    
    assertEquals("test", config.getString("custom-value"));
}
```

### 4. Clean Up After Tests

```java theme={null}
@AfterEach
void tearDown() {
    // Clean up test files
    File testData = new File("test-data");
    if (testData.exists()) {
        deleteDirectory(testData);
    }
}

private void deleteDirectory(File dir) {
    File[] files = dir.listFiles();
    if (files != null) {
        for (File file : files) {
            if (file.isDirectory()) {
                deleteDirectory(file);
            } else {
                file.delete();
            }
        }
    }
    dir.delete();
}
```

## Running Tests

<CodeGroup>
  ```bash Maven theme={null}
  # Run all tests
  mvn test

  # Run specific test class
  mvn test -Dtest=HelloCommandTest

  # Run with coverage
  mvn test jacoco:report
  ```

  ```bash Gradle theme={null}
  # Run all tests
  ./gradlew test

  # Run specific test class
  ./gradlew test --tests HelloCommandTest

  # Run with coverage
  ./gradlew test jacocoTestReport
  ```
</CodeGroup>

## Debugging

When debugging your plugin:

<Steps>
  <Step title="Enable debug logging">
    ```java theme={null}
    @Override
    public void onEnable() {
        getLogger().setLevel(Level.FINE);
        getLogger().fine("Debug logging enabled");
    }
    ```
  </Step>

  <Step title="Add strategic logging">
    ```java theme={null}
    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        getLogger().info("Player joining: " + event.getPlayer().getName());
        // Event handling logic
        getLogger().info("Join handling complete");
    }
    ```
  </Step>

  <Step title="Use IDE debugger">
    Attach your IDE debugger to the running Paper server:

    ```bash theme={null}
    java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 \
         -jar paper-1.21.11-latest.jar
    ```

    Then connect your IDE to port 5005.
  </Step>
</Steps>

<Tip>
  The Paper test-plugin module serves as an excellent reference for implementing various plugin features. Examine it at `test-plugin/` in the Paper repository.
</Tip>

## Continuous Integration

Automate testing with CI/CD:

```yaml .github/workflows/test.yml theme={null}
name: Test Plugin

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up JDK 21
        uses: actions/setup-java@v3
        with:
          java-version: '21'
          distribution: 'temurin'
      
      - name: Run tests
        run: mvn test
      
      - name: Build plugin
        run: mvn package
```

<Note>
  Always test your plugin thoroughly before deploying to a production server. Use a dedicated test server to avoid disrupting players.
</Note>
