AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Drupal Unit Test

skill-trebormc-drupal-ai-agents-drupal-unit-test · by trebormc

>-

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-trebormc-drupal-ai-agents-drupal-unit-test

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-trebormc-drupal-ai-agents-drupal-unit-test)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Drupal Unit Test? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Environment

All commands via ssh web. Use $DDEV_DOCROOT for paths. Detect test gaps: ssh web drush audit:run phpunit --filter="module:MODULE" --format=json

Drupal 10+11 Compatibility (CRITICAL)

Use PHPDoc annotations only — NEVER PHP 8 attributes. Drupal 10 = PHPUnit 9.x (no attribute support).

| Use THIS | NOT this | |---|---| | @coversDefaultClass \My\Class | #[CoversClass(MyClass::class)] | | @covers ::methodName | #[Covers('methodName')] | | @group mymodule | #[Group('mymodule')] | | @dataProvider providerName | #[DataProvider('providerName')] |

Workflow

  1. Read source class in src/
  2. Check existing tests in tests/src/Unit/
  3. Generate test class following templates below
  4. Run test (Form ROOT — requires project phpunit.xml; if missing, use Form CORE from the drupal-testing skill):

ssh web ./vendor/bin/phpunit $DDEV_DOCROOT/modules/custom/MODULE/tests/src/Unit/Service/MyServiceTest.php

  1. Run PHPCS — always try Audit module first:

``bash # Preferred: Audit module (check if installed first) ssh web drush audit:run phpcs --filter="module:MODULE" --format=json # Fallback only if Audit module not installed: ssh web ./vendor/bin/phpcs --standard=Drupal,DrupalPractice $DDEV_DOCROOT/modules/custom/MODULE/tests/src/Unit/ ``

File Structure & Namespace

$DDEV_DOCROOT/modules/custom/MODULE/tests/src/Unit/
├── Service/MyServiceTest.php        # Drupal\Tests\MODULE\Unit\Service
├── Plugin/Block/MyBlockTest.php     # Drupal\Tests\MODULE\Unit\Plugin\Block
├── Form/MyFormTest.php              # Drupal\Tests\MODULE\Unit\Form
└── Controller/MyControllerTest.php  # Drupal\Tests\MODULE\Unit\Controller

Template: Service Test (Complete Example)

configFactory = $this->createMock(ConfigFactoryInterface::class);
    $this->service = new MyService($this->configFactory);
  }

  /**
   * @covers ::process
   */
  public function testProcessValidInput(): void {
    $config = $this->createMock(ImmutableConfig::class);
    $config->method('get')->willReturn('value');
    $this->configFactory->method('get')->willReturn($config);
    $result = $this->service->process('test');
    $this->assertIsArray($result);
    $this->assertNotEmpty($result);
  }

  /**
   * @covers ::process
   * @dataProvider processDataProvider
   */
  public function testProcessScenarios(string $input, bool $expectEmpty): void {
    $config = $this->createMock(ImmutableConfig::class);
    $config->method('get')->willReturn('default');
    $this->configFactory->method('get')->willReturn($config);
    $result = $this->service->process($input);
    $this->assertEquals($expectEmpty, empty($result));
  }

  /**
   * @return array
   *   Test scenarios.
   */
  public static function processDataProvider(): array {
    return [
      'valid input' => ['valid', FALSE],
      'another case' => ['other', FALSE],
    ];
  }

}

Template: Plugin setUp (Reflection for DI)

protected function setUp(): void {
  parent::setUp();
  $this->block = new MyBlock([], 'my_block', ['id' => 'my_block', 'provider' => 'mymodule']);
  // Inject mock via reflection (ONLY for DI, never for testing logic).
  $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
  $ref = new \ReflectionClass($this->block);
  $prop = $ref->getProperty('entityTypeManager');
  $prop->setAccessible(TRUE);
  $prop->setValue($this->block, $this->entityTypeManager);
}

Template: Form Test Methods

/** @covers ::buildForm */
public function testBuildFormStructure(): void {
  $result = $this->form->buildForm([], new FormState());
  $this->assertIsArray($result);
  $this->assertArrayHasKey('actions', $result);
}

/** @covers ::validateForm */
public function testValidateFormInvalidData(): void {
  $form = [];
  $form_state = new FormState();
  $form_state->setValues(['name' => '']);
  $this->form->validateForm($form, $form_state);
  $this->assertTrue($form_state->hasAnyErrors());
}

Common Mock Patterns

// Config Factory
$config = $this->createMock(ImmutableConfig::class);
$config->method('get')->willReturnCallback(fn(string $key) => $values[$key] ?? NULL);
$this->configFactory->method('get')->willReturn($config);

// Entity Query (always include accessCheck)
$query = $this->createMock(QueryInterface::class);
$query->method('accessCheck')->willReturnSelf();
$query->method('condition')->willReturnSelf();
$query->method('execute')->willReturn(['id1', 'id2']);
$storage = $this->createMock(EntityStorageInterface::class);
$storage->method('getQuery')->willReturn($query);
$this->entityTypeManager->method('getStorage')->willReturn($storage);

// Logger with assertion
$this->logger = $this->createMock(LoggerInterface::class);
$this->logger->expects($this->once())->method('error')
  ->with($this->stringContains('failed'));

// String translation — already available from UnitTestCase:
// $this->getStringTranslationStub()

Advanced Mock: EntityTypeManager Chain

// Full EntityTypeManager → Storage → Entity mock chain
$entity = $this->createMock(EntityInterface::class);
$entity->method('id')->willReturn('1');
$entity->method('label')->willReturn('Test');

$storage = $this->createMock(EntityStorageInterface::class);
$storage->expects($this->once())
  ->method('load')
  ->with(1)
  ->willReturn($entity);
$storage->method('loadMultiple')
  ->willReturn(['1' => $entity]);

$entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
$entityTypeManager->expects($this->once())
  ->method('getStorage')
  ->with('node')
  ->willReturn($storage);

$service = new MyService($entityTypeManager);
$result = $service->loadEntity(1);
$this->assertNotNull($result);

PHPUnit Configuration (phpunit.xml)

Place this file at the PROJECT ROOT. It is what enables the simple "Form ROOT" commands (no -c flag, no env vars).

WARNING — BEFORE copying this template: it uses web/ as the docroot. Check the real docroot with grep "^docroot:" .ddev/config.yaml and replace EVERY web/ below if it differs (e.g. docroot/):


  
    
      web/modules/custom/*/tests/src/Unit
    
    
      web/modules/custom/*/tests/src/Kernel
    
    
      web/modules/custom/*/tests/src/Functional
    
  
  
    
    
    
  

For PHPCS, PHPStan, Rector, and GrumPHP configuration, see the quality-tools-setup rule.

Common Testing Pitfalls

  1. Testing implementation, not behavior — Assert on results (WHAT), not internal calls (HOW). Don't expects($this->exactly(N)) on internal helpers.
  2. Over-mocking — Only mock external deps (DB, HTTP, filesystem). 4+ mocks = code needs refactoring. Use real value objects.
  3. Shared state — Never use static props between tests. Use setUp() for fresh state each test.
  4. Wrong test type — Don't use BrowserTestBase for pure logic. Use UnitTestCase when no Drupal bootstrap needed.
  5. Time-dependent tests — Never sleep(). Inject time as dependency, test with controlled timestamps.

Test Debugging Commands

# Run single test with verbose output
ssh web ./vendor/bin/phpunit --filter testMethodName path/to/Test.php -v

# Run tests with debug info
ssh web ./vendor/bin/phpunit $DDEV_DOCROOT/modules/custom/mymodule --debug

# List all tests without running
ssh web ./vendor/bin/phpunit --list-tests $DDEV_DOCROOT/modules/custom/mymodule

# Run with testdox output
ssh web ./vendor/bin/phpunit --testdox $DDEV_DOCROOT/modules/custom/mymodule

Troubleshooting

| Error | Cause | Fix | |-------|-------|-----| | Class not found | Stale autoloader or wrong namespace | ssh web composer dump-autoload; verify namespace matches the directory path | | Cannot mock final class | createMock() on a final class | Mock the interface instead; if there is none, test through a wrapper or use a Kernel test | | setUp needs more than 4-5 mocks | Class is too coupled for unit testing | Switch to a Kernel test (drupal-kernel-test skill) | | Data provider error on Drupal 11 | Provider method is not static | Make all @dataProvider methods public static function | | SIMPLETEST_DB / DB connection errors | Unit test accidentally touching Drupal | Pure unit tests need no DB — remove the dependency or switch to Kernel test |

Related Skills

  • drupal-testing — Full test lifecycle: kernel tests, functional tests, test execution, coverage (use for non-unit test types)
  • quality-checks — Code quality validation after writing tests
  • drupal-debugging — Test debugging commands and troubleshooting

Rules

  1. Base class: Drupal\Tests\UnitTestCase (never PHPUnit\Framework\TestCase)
  2. PHPDoc annotations only — no PHP 8 attributes
  3. declare(strict_types=1) first line after <?php
  4. All dependencies mocked — no DB, filesystem, or HTTP
  5. No \Drupal::service() in tests — DI via constructor or reflection
  6. No sleep() — use controlled time objects
  7. 2-space indentation (Drupal standard)
  8. Test public API only — reflection only for injecting mock dependencies
  9. One assertion concept per test method
  10. Descriptive names: testProcessReturnsEmptyArrayWhenNoData

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.