Skip to content
Back to skills

Dotkernel Testing

ASecurity

Use when writing, running or fixing tests in a Dotkernel project — unit tests, functional route tests, mocking repositories and services, authenticating a test request, seeding data, or diagnosing failures inside setUp such as "test mode is NOT enabled" or "non in-memory database". Also use when deciding what coverage a new route needs.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
testinggophpbashsqltestingapidatabasefrontend

Works with

  • api

Security analysis

A100/100

Scanned November 7, 2026

npx -y skills add dotkernel/dotboost --skill dotkernel-testing --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dotkernel Testing?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dotkernel Testing
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/dotkernel-dotkernel-testing/badge)](https://www.skillsdirectory.com/skills/dotkernel-dotkernel-testing)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dotkernel-testing
description: >
  Use when writing, running or fixing tests in a Dotkernel project — unit tests, functional
  route tests, mocking repositories and services, authenticating a test request, seeding data, or
  diagnosing failures inside setUp such as "test mode is NOT enabled" or "non in-memory
  database". Also use when deciding what coverage a new route needs.
---

# Testing

```text
Applies to
  Projects:  - all
  Packages:  - none
```

```bash
composer test
php ./vendor/bin/phpunit --testsuite UnitTests
php ./vendor/bin/phpunit --testsuite FunctionalTests
php ./vendor/bin/phpunit --filter BookTest
```

Dotkernel `phpunit.xml` files commonly set `stopOnError` and `stopOnFailure`, so the run halts at
the first red test. Fix them one at a time.

## Prerequisite: the test config

Functional tests need a test-specific local config — commonly
`config/autoload/local.test.php`, copied from its `.dist` — pointing Doctrine at an in-memory
SQLite database. Without it, the base test case aborts with a message like:

- *"test mode is NOT enabled"* → the test config file is missing.
- *"running tests in a non in-memory database"* → present but not configured for memory.

**Never "fix" either by pointing tests at a real database.** That is the guard working.

The stock Frontend also ships `local.test.php.dist`, but it has no functional suite, and no base
class in its `test/Common/` enforces test mode, so its unit tests do not need the copy.

## Unit tests

`test/Unit/<Module>/<Layer>/<Class>Test.php`, namespace per `autoload-dev`.

Target services, middleware, attributes and helpers. Mock the repository; assert behaviour and
the exceptions:

```php
$repository = $this->createMock(BookRepository::class);
$repository->method('find')->willReturn(null);

$this->expectException(NotFoundException::class);
(new BookService($repository))->findBook('…');
```

Do not boot the container in a unit test. If you need the container, it is a functional test.

## Functional tests

Only the stock API and Admin ship a functional suite. Frontend and Light have unit tests only, and
Queue keeps its tests in `test/App/` and `test/Swoole/`. Do not add a functional suite to those
without asking — it means new base classes and test config.

`test/Functional/<Module>Test.php`, extending the repo's functional base class. **Read it before
writing anything** — the two stock base classes differ a lot.

**Admin** (`test/Functional/FunctionalTest.php` on the stock `7.0` branch) boots the container and
pipeline and offers only a `get()` request helper, `getContainer()`, `getEntityManager()` and
`runMigrations()`, which builds the schema from entity metadata. It has no status assertions,
entity factories, fixtures, login helper or service-replacement helper: assert on the response
directly and create data through the entity manager. Ask before adding helpers to the base class.

**API** (`test/Functional/AbstractFunctionalTest.php` on the stock `7.0` branch) boots the real
container, pipeline and routes, then builds the schema and loads every fixture in `setUp()` —
isolated per test, but not free. Keep tests purposeful. It provides:

- Request helpers per verb, pre-setting the right `Accept` header and attaching auth when logged
  in.
- Named status assertions (ok, created, no-content, bad-request, unauthorized, forbidden,
  not-found, conflict, gone) — use these rather than raw status comparisons.
- Entity factories and valid/invalid data builders for the project's core entities.
- Credential builders for the project's auth mechanism, plus deliberately invalid twins.
- A service-replacement helper for swapping container services — **use it so tests never send
  real email or make real outbound calls.**
- Direct entity-manager access for persistence setup.

Shape:

```php
class BookTest extends AbstractFunctionalTest
{
    public function testUserCanListBooks(): void
    {
        $identity = $this->createUser()->getIdentity();
        $this->loginAs($identity, self::DEFAULT_PASSWORD);

        $this->assertResponseOk($this->get('/book'));
    }

    public function testGuestCannotListBooks(): void
    {
        $this->assertResponseForbidden($this->get('/book'));
    }
}
```

## Required coverage for a new route

| Case | Expected |
| --- | --- |
| Happy path | 200 / 201 / 204 / rendered page — assert the payload or output, not just the status |
| Validation failure | 400 with the errors payload, or the form re-rendered with messages |
| Not authenticated | API: 403 — a request without a token runs as guest and is refused unless the route is granted to guest. Session-based project: a redirect to login |
| Authenticated, wrong role | 403 — **the case people skip.** Admin / Frontend: this is the test that catches a missing guard rule, because an unlisted route is open to everyone. API: a missing entry denies every role, so the happy path fails instead; this test catches a route granted to too many roles |
| Unknown identifier | 404 |
| Duplicate / conflicting write | 409 where applicable |

For a deprecated API route, also assert the `Sunset` header; for a templated page moved to a new
URL, assert the permanent redirect from the old one. For a form, also assert that a missing or
invalid CSRF token is rejected.

## Conventions

- Test method names read as sentences: `testAdminCannotDeleteOwnAccount`.
- Assert with `StatusCodeInterface::STATUS_*`, never bare integers.
- `test/` is covered by phpcs and phpstan too — tests must pass the same style and type checks.
- Never `markTestSkipped()` a test you broke, and never weaken an assertion to get green.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.