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']);
  }
}

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

  • Wrong: Trying to achieve 100% coverage immediately → Right: Start with 30-40% coverage of critical paths
  • Wrong: Writing performance tests before features stabilize → Right: Add performance tests in final phase
  • Wrong: Skipping base test classes → Right: Create abstract base classes to reduce duplication
  • Wrong: Not prioritizing high-risk code → Right: Test critical business logic first
  • Wrong: Writing all tests at once → Right: Progressive approach provides early feedback

See Also