Skip to content

Progressive Testing Strategy

When to Use

Use this progressive approach when building testing coverage for a new module or improving coverage for an existing module. Start simple and add complexity as the module matures.

Decision: Testing Coverage Phases

Phase Coverage Test Types Focus
MVP 30-40% Unit + Kernel Core logic and integration
Feature Complete 60-70% + Functional Admin UI and workflows
Production Ready 80%+ + FunctionalJS + Gander JavaScript and performance

Pattern: Phase 1 - Foundation Testing (MVP)

// Create abstract base classes for consistency

// tests/src/Unit/MyModuleUnitTestBase.php
namespace Drupal\Tests\my_module\Unit;
use Drupal\Tests\UnitTestCase;

abstract class MyModuleUnitTestBase extends UnitTestCase {
  // Common mocks and setup for unit tests
  protected function getConfigStub(array $config = []): object {
    return $this->getConfigFactoryStub([
      'my_module.settings' => $config,
    ]);
  }
}

// tests/src/Kernel/MyModuleKernelTestBase.php
namespace Drupal\Tests\my_module\Kernel;
use Drupal\KernelTests\KernelTestBase;

abstract class MyModuleKernelTestBase extends KernelTestBase {
  protected static $modules = ['system', 'user', 'my_module'];

  protected function setUp(): void {
    parent::setUp();
    $this->installEntitySchema('user');
    $this->installConfig(['my_module']);
  }
}

// tests/src/Kernel/EntityProcessorTest.php
// The base class stays bare; the class PHPUnit runs carries the attribute.
namespace Drupal\Tests\my_module\Kernel;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;

#[Group('my_module')]
#[RunTestsInSeparateProcesses]
class EntityProcessorTest extends MyModuleKernelTestBase {
  public function testProcessing(): void {
    $processor = $this->container->get('my_module.entity_processor');
    $this->assertNotNull($processor);
  }
}

Priority: Test core business logic and Drupal integration first.

Pattern: Phase 2 - Feature Testing

// tests/src/Functional/MyModuleFunctionalTestBase.php
namespace Drupal\Tests\my_module\Functional;
use Drupal\Tests\BrowserTestBase;

abstract class MyModuleFunctionalTestBase extends BrowserTestBase {
  protected $defaultTheme = 'stark';
  protected static $modules = ['my_module'];

  protected function setUp(): void {
    parent::setUp();

    // Create standard test users
    $this->adminUser = $this->drupalCreateUser([
      'administer my module',
      'access administration pages',
    ]);
  }
}

Priority: Add admin interface and user workflow testing.

Pattern: Phase 3 - Production Ready

// tests/src/FunctionalJavascript/MyModuleJavaScriptTestBase.php
namespace Drupal\Tests\my_module\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;

abstract class MyModuleJavaScriptTestBase extends WebDriverTestBase {
  protected $defaultTheme = 'stark';
  protected static $modules = ['my_module'];
}

// tests/src/FunctionalJavascript/MyModulePerformanceTestBase.php
namespace Drupal\Tests\my_module\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\PerformanceTestBase;

abstract class MyModulePerformanceTestBase extends PerformanceTestBase {
  protected static $modules = ['my_module'];
  protected $profile = 'minimal';
}

Priority: Add JavaScript interactions and performance validation.

Common Mistakes

  • Trying to achieve 100% coverage immediately → Burnout and abandoned testing
  • Writing performance tests before features stabilize → Wasted effort on changing code
  • Skipping base test classes → Code duplication across test files
  • Not prioritizing high-risk code → Testing trivial code while critical paths untested
  • Writing all tests at once → No early feedback, integration issues found late

WHY these are mistakes: Testing is an investment. Start with high-value tests that catch the most bugs. Building complete coverage takes time - progressive approach maintains momentum. Base classes reduce duplication and make tests easier to maintain. Performance testing unstable features wastes time because budgets change constantly. Early testing provides early feedback - don't wait until all features are complete.

See Also