Skip to main content

Testing

Polyslug ships assertions so your suite can state what a URL should do, rather than re-implementing the checks by hand.

use Polyslug\Testing\InteractsWithPolyslug;

uses(InteractsWithPolyslug::class); // Pest — or `use InteractsWithPolyslug;` in a PHPUnit TestCase

$this->assertHasCurrentSlug($page, 'hello-world');
$this->assertSlugResolves(Page::class, $page->getRouteKey(), $page->id);
$this->assertSlugRedirects('/pages/old-slug_TOKEN', '/pages/new-slug_TOKEN');
$this->assertSlugNotResolvable(Page::class, 'bad_token');
AssertionAsserts
assertHasCurrentSlug($model, $slug, $locale = null)The model's current slug for a locale.
assertSlugResolves($modelClass, $routeKey, $expectedKey)A route key resolves to the expected model.
assertSlugRedirects($from, $to, $status = 301)A GET to $from self-heals to the canonical $to with the given status.
assertSlugNotResolvable($modelClass, $token)A token does not resolve — a clean 404, not a fuzzy match.

What is worth asserting

  • The redirect, not just the slug. assertSlugRedirects() is the one that proves self-healing is actually wired: a mis-ordered middleware stack leaves the slug correct and the redirect silently absent. See the Route::polyslug() macro.
  • The negative case. assertSlugNotResolvable() is how you prove a gate holds. Assert it for a foreign tenant's key and for a draft, not only for a malformed token — a resolution gate that stopped working still passes every positive test.
  • Each locale you ship. assertHasCurrentSlug($model, $slug, 'de') catches the case where a locale silently falls back to the default language's slug.

The package's own suite

Polyslug's suite is not part of the published package. composer test on a clone of the public repository has nothing to run: the mirror carries src, config, database, resources and the release metadata, and the tests, the workbench application and the tooling configuration stay in the private development repository.

What it covers is worth knowing anyway, because it is what a release is measured against. The fast suites run on in-memory SQLite. On top of them the same behavior is re-run against real PostgreSQL 18 and MySQL 8.4 servers, because the one-current-slug guarantee is not one mechanism: on PostgreSQL and SQLite it is a functional partial unique index, and on MySQL it is a set of virtual generated key columns. SQLite proves neither of the other two, so a green SQLite run is a starting point rather than an answer.

A pull request against the public repository is merged into the private one first and put through that bar there, so a contribution does not need any of it locally — see CONTRIBUTING.md.