|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +title: Testing Plugins |
| 4 | +parent: Advanced Topics |
| 5 | +nav_order: 5 |
| 6 | +--- |
| 7 | + |
| 8 | +# Testing Unraid® Plugins |
| 9 | + |
| 10 | +{: .warning } |
| 11 | +> This page is a stub. [Help us expand it!](https://github.com/mstrhakr/plugin-docs/blob/main/CONTRIBUTING.md) |
| 12 | +
|
| 13 | +{: .note } |
| 14 | +> Unraid® is a registered trademark of Lime Technology, Inc. This documentation is not affiliated with Lime Technology, Inc. |
| 15 | +
|
| 16 | +## Overview |
| 17 | + |
| 18 | +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. |
| 19 | + |
| 20 | +## The Challenge |
| 21 | + |
| 22 | +Plugins rely on: |
| 23 | +- **Unraid globals**: `$var`, `$disks`, `$shares` arrays |
| 24 | +- **Helper functions**: `parse_plugin_cfg()`, `autov()`, `csrf_token()` |
| 25 | +- **System state**: Array status, Docker daemon, network configuration |
| 26 | +- **File paths**: `/usr/local/emhttp/`, `/boot/config/plugins/` |
| 27 | + |
| 28 | +These dependencies make traditional unit testing difficult without a running Unraid system. |
| 29 | + |
| 30 | +## Testing Strategies |
| 31 | + |
| 32 | +### Static Analysis (Recommended Starting Point) |
| 33 | + |
| 34 | +Static analysis catches bugs without executing code. These tools work in any development environment: |
| 35 | + |
| 36 | +| Tool | Purpose | Language | |
| 37 | +|------|---------|----------| |
| 38 | +| **PHPStan** | Type checking, bug detection | PHP | |
| 39 | +| **PHP-CS-Fixer** | Code style/formatting | PHP | |
| 40 | +| **ShellCheck** | Bash linting and best practices | Bash | |
| 41 | +| **commitlint** | Commit message conventions | Any | |
| 42 | + |
| 43 | +See the [Unraid Plugin Template](https://github.com/dkaser/unraid-plugin-template) for PHPStan/PHP-CS-Fixer configuration examples. |
| 44 | + |
| 45 | +### Bash Script Testing with BATS |
| 46 | + |
| 47 | +[BATS (Bash Automated Testing System)](https://github.com/bats-core/bats-core) enables unit testing for shell scripts. |
| 48 | + |
| 49 | +```bash |
| 50 | +#!/usr/bin/env bats |
| 51 | +# test_compose.bats |
| 52 | + |
| 53 | +setup() { |
| 54 | + # Create mock environment |
| 55 | + export MOCK_MODE=1 |
| 56 | + source ./scripts/compose.sh |
| 57 | +} |
| 58 | + |
| 59 | +@test "parse_stack_name extracts name from path" { |
| 60 | + result=$(parse_stack_name "/mnt/user/appdata/mystack/docker-compose.yml") |
| 61 | + [ "$result" = "mystack" ] |
| 62 | +} |
| 63 | + |
| 64 | +@test "validate_compose_file detects missing file" { |
| 65 | + run validate_compose_file "/nonexistent/docker-compose.yml" |
| 66 | + [ "$status" -eq 1 ] |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +**Key techniques:** |
| 71 | +- Extract testable functions that don't depend on system state |
| 72 | +- Use environment variables to enable "mock mode" |
| 73 | +- Stub external commands (`docker`, `logger`, etc.) |
| 74 | + |
| 75 | +### PHP Testing with PHPUnit |
| 76 | + |
| 77 | +[PHPUnit](https://phpunit.de/) is the standard PHP testing framework. |
| 78 | + |
| 79 | +```php |
| 80 | +<?php |
| 81 | +// tests/UtilTest.php |
| 82 | +use PHPUnit\Framework\TestCase; |
| 83 | + |
| 84 | +class UtilTest extends TestCase |
| 85 | +{ |
| 86 | + public function testParseConfig(): void |
| 87 | + { |
| 88 | + $config = "setting1=\"value1\"\nsetting2=\"value2\""; |
| 89 | + $result = parse_config_string($config); |
| 90 | + |
| 91 | + $this->assertEquals('value1', $result['setting1']); |
| 92 | + $this->assertEquals('value2', $result['setting2']); |
| 93 | + } |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +**Mocking Unraid functions:** |
| 98 | + |
| 99 | +```php |
| 100 | +<?php |
| 101 | +// tests/bootstrap.php - Mock Unraid functions |
| 102 | + |
| 103 | +if (!function_exists('parse_plugin_cfg')) { |
| 104 | + function parse_plugin_cfg($plugin) { |
| 105 | + // Return mock config for testing |
| 106 | + return [ |
| 107 | + 'setting1' => 'test_value', |
| 108 | + 'debug' => 'yes' |
| 109 | + ]; |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +if (!function_exists('autov')) { |
| 114 | + function autov($path) { |
| 115 | + return $path . '?v=test'; |
| 116 | + } |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +### Integration Testing on Live Unraid |
| 121 | + |
| 122 | +Some tests require a running Unraid system. Options include: |
| 123 | + |
| 124 | +1. **Manual testing** - Install plugin, verify behavior |
| 125 | +2. **SSH-based test scripts** - Run validation scripts remotely |
| 126 | +3. **VM testing** - Unraid trial in VirtualBox/VMware |
| 127 | +4. **Docker-based simulation** - Limited, but useful for some scenarios |
| 128 | + |
| 129 | +```bash |
| 130 | +# Example: Remote validation script |
| 131 | +ssh root@unraid-server "bash -s" < ./tests/integration/test_event_handlers.sh |
| 132 | +``` |
| 133 | + |
| 134 | +## CI/CD Integration |
| 135 | + |
| 136 | +### GitHub Actions Example |
| 137 | + |
| 138 | +```yaml |
| 139 | +name: Test & Lint |
| 140 | + |
| 141 | +on: [push, pull_request] |
| 142 | + |
| 143 | +jobs: |
| 144 | + shellcheck: |
| 145 | + runs-on: ubuntu-latest |
| 146 | + steps: |
| 147 | + - uses: actions/checkout@v4 |
| 148 | + - name: Run ShellCheck |
| 149 | + uses: ludeeus/action-shellcheck@master |
| 150 | + with: |
| 151 | + scandir: './source' |
| 152 | + |
| 153 | + phpstan: |
| 154 | + runs-on: ubuntu-latest |
| 155 | + steps: |
| 156 | + - uses: actions/checkout@v4 |
| 157 | + - uses: php-actions/composer@v6 |
| 158 | + - uses: php-actions/phpstan@v3 |
| 159 | + |
| 160 | + bats: |
| 161 | + runs-on: ubuntu-latest |
| 162 | + steps: |
| 163 | + - uses: actions/checkout@v4 |
| 164 | + - name: Install BATS |
| 165 | + run: | |
| 166 | + git clone https://github.com/bats-core/bats-core.git |
| 167 | + cd bats-core && sudo ./install.sh /usr/local |
| 168 | + - name: Run tests |
| 169 | + run: bats tests/*.bats |
| 170 | +``` |
| 171 | +
|
| 172 | +## Testable Code Patterns |
| 173 | +
|
| 174 | +### Extract Pure Functions |
| 175 | +
|
| 176 | +Move logic that doesn't depend on Unraid into separate, testable functions: |
| 177 | +
|
| 178 | +```php |
| 179 | +<?php |
| 180 | +// Before: Hard to test |
| 181 | +function get_stack_status($path) { |
| 182 | + global $var; |
| 183 | + $cfg = parse_plugin_cfg('compose.manager'); |
| 184 | + // ... complex logic using $var and $cfg |
| 185 | +} |
| 186 | + |
| 187 | +// After: Testable |
| 188 | +function calculate_status($containers, $expected_count, $config) { |
| 189 | + // Pure function - no globals, easy to test |
| 190 | + if (count($containers) === 0) return 'stopped'; |
| 191 | + if (count($containers) < $expected_count) return 'partial'; |
| 192 | + return 'running'; |
| 193 | +} |
| 194 | + |
| 195 | +function get_stack_status($path) { |
| 196 | + global $var; |
| 197 | + $cfg = parse_plugin_cfg('compose.manager'); |
| 198 | + $containers = get_containers($path); |
| 199 | + return calculate_status($containers, $cfg['expected'], $cfg); |
| 200 | +} |
| 201 | +``` |
| 202 | + |
| 203 | +### Dependency Injection |
| 204 | + |
| 205 | +```php |
| 206 | +<?php |
| 207 | +// Inject dependencies instead of using globals |
| 208 | +function process_stack($path, $config = null, $logger = null) { |
| 209 | + $config = $config ?? parse_plugin_cfg('compose.manager'); |
| 210 | + $logger = $logger ?? 'logger'; |
| 211 | + |
| 212 | + // Now testable with mock config and logger |
| 213 | +} |
| 214 | +``` |
| 215 | + |
| 216 | +## Project Structure for Testing |
| 217 | + |
| 218 | +``` |
| 219 | +myplugin/ |
| 220 | +├── source/ |
| 221 | +│ └── usr/local/emhttp/plugins/myplugin/ |
| 222 | +│ ├── *.page |
| 223 | +│ ├── php/ |
| 224 | +│ │ └── util.php |
| 225 | +│ └── scripts/ |
| 226 | +│ └── main.sh |
| 227 | +├── tests/ |
| 228 | +│ ├── bootstrap.php # Mock Unraid functions |
| 229 | +│ ├── unit/ |
| 230 | +│ │ ├── UtilTest.php |
| 231 | +│ │ └── main.bats |
| 232 | +│ └── integration/ |
| 233 | +│ └── test_on_server.sh |
| 234 | +├── composer.json # PHPUnit, PHPStan |
| 235 | +├── phpstan.neon |
| 236 | +├── phpunit.xml |
| 237 | +└── .github/workflows/ |
| 238 | + └── test.yml |
| 239 | +``` |
| 240 | + |
| 241 | +## Resources |
| 242 | + |
| 243 | +- [BATS Core](https://github.com/bats-core/bats-core) - Bash testing framework |
| 244 | +- [PHPUnit](https://phpunit.de/) - PHP testing framework |
| 245 | +- [PHPStan](https://phpstan.org/) - PHP static analysis |
| 246 | +- [ShellCheck](https://www.shellcheck.net/) - Shell script analysis |
| 247 | +- [Unraid Plugin Template](https://github.com/dkaser/unraid-plugin-template) - PHPStan/linting setup |
| 248 | + |
| 249 | +## Related Topics |
| 250 | + |
| 251 | +- [Debugging Techniques]({% link docs/advanced/debugging-techniques.md %}) |
| 252 | +- [Build and Packaging]({% link docs/build-and-packaging.md %}) |
| 253 | + |
0 commit comments