Currently Available: Need a skilled Software Developer for your next project?
Categories
Laravel

Testing Statamic Addons

This guide shows you how to test Statamic addons effectively. Testing addons is different from testing regular PHP packages because they depend on Statamic's core functionality, which often requires a full application environment.

Why Testing Statamic Addons Is Different

Unlike regular PHP packages, Statamic addons operate within a complex ecosystem:

  • They depend on Laravel's service container
  • They interact with Statamic's content repositories
  • They often manipulate Antlers templates and Bard fields
  • They may hook into Statamic's event system

The challenge? Many of these features require a full Statamic installation to function. You can't just mock everything and call it a day.

Getting Started: Your First Test

Let's start with the basics. Every addon should have at least a smoke test to ensure it loads correctly.

Step 1: Create Your Test Structure

First, create a tests directory in your addon:

your-addon/
├── src/
├── resources/
├── tests/
│   ├── Unit/
│   ├── Feature/
│   └── TestCase.php
├── composer.json
└── phpunit.xml

Step 2: Configure PHPUnit

Create a phpunit.xml file in your addon root. This tells PHPUnit how to run your tests:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         backupGlobals="false" 
         bootstrap="tests/bootstrap.php" 
         colors="true">
    <testsuites>
        <testsuite name="Unit">
            <directory>tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory>tests/Feature</directory>
        </testsuite>
    </testsuites>
</phpunit>

Step 3: Set Up Autoloading

Create tests/bootstrap.php to ensure your test classes are autoloaded:

<?php

// Register test namespace
$loader = require __DIR__ . '/../../../vendor/autoload.php';
$loader->addPsr4('YourVendor\\YourAddon\\Tests\\', __DIR__);

This step is crucial! Without it, PHPUnit won't find your test classes.

Step 4: Install Testing Dependencies

Update your addon's composer.json:

{
    "require-dev": {
        "orchestra/testbench": "^9.0",
        "mockery/mockery": "^1.6",
        "phpunit/phpunit": "^10.0"
    }
}

Important: Install these in your main Laravel project, not just the addon:

# If using Laravel Sail
./vendor/bin/sail composer require --dev orchestra/testbench mockery/mockery

# Regular Composer
composer require --dev orchestra/testbench mockery/mockery

Your First Real Test

Let's write a basic test to ensure your addon loads correctly:

<?php

namespace YourVendor\YourAddon\Tests\Unit;

use PHPUnit\Framework\TestCase;
use YourVendor\YourAddon\ServiceProvider;

class BasicSmokeTest extends TestCase
{
    /** @test */
    public function the_addon_can_be_instantiated()
    {
        $provider = new ServiceProvider(null);

        $this->assertInstanceOf(ServiceProvider::class, $provider);
    }

    /** @test */
    public function required_services_exist()
    {
        // Check that your main service classes exist
        $this->assertTrue(class_exists(\YourVendor\YourAddon\Services\YourService::class));
        $this->assertTrue(class_exists(\YourVendor\YourAddon\Http\Controllers\YourController::class));
    }
}

This might seem trivial, but it catches a surprising number of issues early!

Running Your Tests

If your main project uses Laravel Sail, you'll need to run tests inside the container:

# Run all addon tests
./vendor/bin/sail exec laravel.test bash -c "cd addons/your-addon && /var/www/html/vendor/bin/phpunit"

# Run with detailed output
./vendor/bin/sail exec laravel.test bash -c "cd addons/your-addon && /var/www/html/vendor/bin/phpunit --testdox"

# Run a specific test file
./vendor/bin/sail exec laravel.test bash -c "cd addons/your-addon && /var/www/html/vendor/bin/phpunit tests/Unit/YourTest.php"

Pro tip: Add these as scripts in your addon's composer.json:

{
    "scripts": {
        "test": "cd ../../ && ./vendor/bin/sail exec laravel.test bash -c 'cd addons/your-addon && /var/www/html/vendor/bin/phpunit'",
        "test:unit": "cd ../../ && ./vendor/bin/sail exec laravel.test bash -c 'cd addons/your-addon && /var/www/html/vendor/bin/phpunit --testsuite=Unit'"
    }
}

Testing Strategies for Different Addon Types

1. Addons That Modify Content

If your addon manipulates Bard fields, entries, or collections, focus on testing the transformation logic:

/** @test */
public function it_transforms_content_correctly()
{
    // Create a simple data structure that mimics Statamic's format
    $inputData = [
        'title' => 'My Article',
        'content' => 'Original content'
    ];

    // Test your transformation logic
    $transformer = new ContentTransformer();
    $result = $transformer->transform($inputData);

    // Assert the transformation worked
    $this->assertEquals('Expected content', $result['content']);
}

Key insight: Test your business logic separately from Statamic's persistence layer.

2. Addons That Add Tags or Fieldtypes

For custom tags and fieldtypes, test the rendering logic:

/** @test */
public function custom_tag_renders_correctly()
{
    $tag = new MyCustomTag();
    $tag->setParameters(['limit' => 5]);
    $tag->setContent('some content');

    $output = $tag->index();

    $this->assertStringContainsString('expected output', $output);
}

3. Addons That Provide API Endpoints

Test your controllers by mocking the dependencies:

use Mockery;
use Illuminate\Http\Request;

/** @test */
public function api_endpoint_returns_correct_data()
{
    $request = Mockery::mock(Request::class);
    $request->shouldReceive('input')
        ->with('search')
        ->andReturn('test query');

    $controller = new ApiController();
    $response = $controller->search($request);

    $this->assertEquals(200, $response->getStatusCode());
    $this->assertArrayHasKey('results', $response->getData(true));
}

Handling Statamic-Specific Challenges

The Facade Problem

Many Statamic features use facades that expect a full application. Here's how to handle them:

use Illuminate\Support\Facades\Config;

/** @test */
public function it_uses_configuration_correctly()
{
    // Mock the Config facade
    Config::shouldReceive('get')
        ->with('my-addon.settings.feature')
        ->once()
        ->andReturn('enabled');

    $service = new MyService();
    $this->assertTrue($service->isFeatureEnabled());
}

protected function tearDown(): void
{
    // Clean up facade mocks
    Config::clearResolvedInstances();
    Mockery::close();
    parent::tearDown();
}

Testing Without a Full Statamic Installation

When you can't avoid Statamic dependencies, create abstraction layers:

// Instead of using Entry::find() directly in your service...
class EntryRepository
{
    public function find($id)
    {
        return Entry::find($id);
    }
}

// ...you can now mock the repository in tests
$repository = Mockery::mock(EntryRepository::class);
$repository->shouldReceive('find')
    ->with('entry-123')
    ->andReturn($mockEntry);

Best Practices and Pro Tips

1. Organize Tests by Purpose

  • Unit/: Test individual classes and methods
  • Feature/: Test complete features with multiple components
  • Integration/: Test interaction with Statamic (requires full installation)

2. Create a Base TestCase

Build a base test class with common functionality:

<?php

namespace YourVendor\YourAddon\Tests;

use PHPUnit\Framework\TestCase as BaseTestCase;
use Mockery;

abstract class TestCase extends BaseTestCase
{
    protected function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }

    protected function createMockEntry($data = [])
    {
        $entry = Mockery::mock('Statamic\Entries\Entry');

        foreach ($data as $key => $value) {
            $entry->shouldReceive('get')
                ->with($key)
                ->andReturn($value);
        }

        return $entry;
    }
}

3. Test Critical Paths First

Focus your testing efforts on:

  • Data transformations
  • Business logic
  • API contracts
  • Error handling
  • Edge cases

4. Use Descriptive Test Names

// Good
public function it_throws_exception_when_configuration_is_missing()
public function it_returns_empty_array_when_no_results_found()

// Less helpful
public function testSearch()
public function test1()

Common Pitfalls and Solutions

Pitfall 1: "Class not found" Errors

Solution: Check your bootstrap file and ensure the namespace in your test matches your PSR-4 configuration.

Pitfall 2: Facade Root Not Set

Solution: Either mock the facade or run tests in a full Laravel environment.

Pitfall 3: Database Dependencies

Solution: Use in-memory SQLite for tests or mock your database queries:

Schema::create('test_entries', function ($table) {
    $table->id();
    $table->string('title');
    $table->timestamps();
});

Moving Forward

Testing Statamic addons doesn't have to be overwhelming. Start with:

  1. Basic smoke tests to ensure your addon loads
  2. Unit tests for your core business logic
  3. Integration tests for critical user paths

Remember, the goal isn't 100% coverage. Focus on testing the parts that matter most to your users.

Resources

What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Subscribe to my newsletter

Get new posts when I publish them.

I respect your privacy. Unsubscribe at any time.

Leave a Reply

Your email address will not be published. Required fields are marked *