Testing Unraid® Plugins
This page is a stub. Help us expand it!
Unraid® is a registered trademark of Lime Technology, Inc. This documentation is not affiliated with Lime Technology, Inc.
Overview
Testing Unraid® plugins presents unique challenges since plugins depend on Unraid-specific globals, functions, and system state that don’t exist in a standard development environment. This guide covers strategies for testing both bash scripts and PHP code.
The Challenge
Plugins rely on:
- Unraid globals:
$var,$disks,$sharesarrays - Helper functions:
parse_plugin_cfg(),autov(),csrf_token() - System state: Array status, Docker daemon, network configuration
- File paths:
/usr/local/emhttp/,/boot/config/plugins/
These dependencies make traditional unit testing difficult without a running Unraid system.
Testing Strategies
Static Analysis (Recommended Starting Point)
Static analysis catches bugs without executing code. These tools work in any development environment:
| Tool | Purpose | Language |
|---|---|---|
| PHPStan | Type checking, bug detection | PHP |
| PHP-CS-Fixer | Code style/formatting | PHP |
| ShellCheck | Bash linting and best practices | Bash |
| commitlint | Commit message conventions | Any |
See the Unraid Plugin Template for PHPStan/PHP-CS-Fixer configuration examples.
Bash Script Testing with BATS
BATS (Bash Automated Testing System) enables unit testing for shell scripts.
#!/usr/bin/env bats
# test_compose.bats
setup() {
# Create mock environment
export MOCK_MODE=1
source ./scripts/compose.sh
}
@test "parse_stack_name extracts name from path" {
result=$(parse_stack_name "/mnt/user/appdata/mystack/compose.yaml")
[ "$result" = "mystack" ]
}
@test "validate_compose_file detects missing file" {
run validate_compose_file "/nonexistent/compose.yaml"
[ "$status" -eq 1 ]
}
Key techniques:
- Extract testable functions that don’t depend on system state
- Use environment variables to enable “mock mode”
- Stub external commands (
docker,logger, etc.)
PHP Testing with PHPUnit
PHPUnit is the standard PHP testing framework.
<?php
// tests/UtilTest.php
use PHPUnit\Framework\TestCase;
class UtilTest extends TestCase
{
public function testParseConfig(): void
{
$config = "setting1=\"value1\"\nsetting2=\"value2\"";
$result = parse_config_string($config);
$this->assertEquals('value1', $result['setting1']);
$this->assertEquals('value2', $result['setting2']);
}
}
Mocking Unraid functions:
<?php
// tests/bootstrap.php - Mock Unraid functions
if (!function_exists('parse_plugin_cfg')) {
function parse_plugin_cfg($plugin) {
// Return mock config for testing
return [
'setting1' => 'test_value',
'debug' => 'yes'
];
}
}
if (!function_exists('autov')) {
function autov($path) {
return $path . '?v=test';
}
}
Integration Testing on Live Unraid
Some tests require a running Unraid system. Options include:
- Manual testing - Install plugin, verify behavior
- SSH-based test scripts - Run validation scripts remotely
- VM testing - Unraid trial in VirtualBox/VMware
- Docker-based simulation - Limited, but useful for some scenarios
# Example: Remote validation script
ssh root@unraid-server "bash -s" < ./tests/integration/test_event_handlers.sh
CI/CD Integration
GitHub Actions Example
name: Test & Lint
on: [push, pull_request]
jobs:
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
with:
scandir: './source'
phpstan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: php-actions/composer@v6
- uses: php-actions/phpstan@v3
bats:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install BATS
run: |
git clone https://github.com/bats-core/bats-core.git
cd bats-core && sudo ./install.sh /usr/local
- name: Run tests
run: bats tests/*.bats
Testable Code Patterns
Extract Pure Functions
Move logic that doesn’t depend on Unraid into separate, testable functions:
<?php
// Before: Hard to test
function get_stack_status($path) {
global $var;
$cfg = parse_plugin_cfg('compose.manager');
// ... complex logic using $var and $cfg
}
// After: Testable
function calculate_status($containers, $expected_count, $config) {
// Pure function - no globals, easy to test
if (count($containers) === 0) return 'stopped';
if (count($containers) < $expected_count) return 'partial';
return 'running';
}
function get_stack_status($path) {
global $var;
$cfg = parse_plugin_cfg('compose.manager');
$containers = get_containers($path);
return calculate_status($containers, $cfg['expected'], $cfg);
}
Dependency Injection
<?php
// Inject dependencies instead of using globals
function process_stack($path, $config = null, $logger = null) {
$config = $config ?? parse_plugin_cfg('compose.manager');
$logger = $logger ?? 'logger';
// Now testable with mock config and logger
}
Project Structure for Testing
myplugin/
├── source/
│ └── usr/local/emhttp/plugins/myplugin/
│ ├── *.page
│ ├── php/
│ │ └── util.php
│ └── scripts/
│ └── main.sh
├── tests/
│ ├── bootstrap.php # Mock Unraid functions
│ ├── unit/
│ │ ├── UtilTest.php
│ │ └── main.bats
│ └── integration/
│ └── test_on_server.sh
├── composer.json # PHPUnit, PHPStan
├── phpstan.neon
├── phpunit.xml
└── .github/workflows/
└── test.yml
Resources
- BATS Core - Bash testing framework
- PHPUnit - PHP testing framework
- PHPStan - PHP static analysis
- ShellCheck - Shell script analysis
- Unraid Plugin Template - PHPStan/linting setup