No fake, by design
The Translatable facade has no fake(). Nothing on it writes to the database, queues, mails, dispatches an event or calls out, so there is nothing for a fake to record. To pin behaviour in a test, set the config — or swap in a TranslationManager subclass to override one method; facade calls and every model observe it (see DI and actions):
use RoundlyConsulting\Translatable\Enums\FallbackMode;
use RoundlyConsulting\Translatable\Facades\Translatable;
use RoundlyConsulting\Translatable\Support\TranslationManager;
config()->set('translatable.locales', ['en', 'de']); // the locale set
config()->set('translatable.fallback', FallbackMode::None); // the fallback chain
// or override one method — facade calls and every model observe the subclass:
Translatable::swap(new class extends TranslationManager {
public function supported(): array { return ['en', 'de']; }
});Testing your models
Translations live in an ordinary column, so tests run against real models: set the app locale and config per test, then assert through the public API. The examples assume the default en/sk locales and a Topic model with name fillable; the event test also needs DispatchesTranslationEvents:
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Translatable\Enums\FallbackMode;
use RoundlyConsulting\Translatable\Events\TranslationsChanged;
it('falls back to English for an untranslated locale', function () {
app()->setLocale('sk');
$topic = new Topic(['name' => ['en' => 'Investing']]);
expect($topic->name)->toBe('Investing')
->and($topic->translatedOrNull('name'))->toBeNull()
->and($topic->missingLocales('name'))->toBe(['sk']);
});
it('renders nothing for a missing locale in None mode', function () {
config()->set('translatable.fallback', FallbackMode::None);
app()->setLocale('sk');
$topic = new Topic(['name' => ['en' => 'Investing']]);
expect($topic->name)->toBeNull();
});
it('dispatches TranslationsChanged on create', function () {
Event::fake([TranslationsChanged::class]);
$topic = Topic::create(['name' => ['en' => 'Investing']]);
Event::assertDispatched(TranslationsChanged::class, fn (TranslationsChanged $event): bool =>
$event->model->is($topic) && $event->changedAttributes === ['name']);
});
it('shows the missing-locales badge', function () {
$topic = new Topic(['name' => ['en' => 'Investing']]);
$html = Blade::render('<x-translatable-status :model="$model" />', ['model' => $topic]);
expect($html)->toContain('translatable-status--incomplete');
});The package’s own suite
The suite runs on SQLite by default. Point it at a real engine with the standard TESTING_DB_* variables — TESTING_DB_DRIVER is the one that actually moves it:
composer test
TESTING_DB_DRIVER=pgsql TESTING_DB_HOST=127.0.0.1 TESTING_DB_PORT=5432 \
TESTING_DB_DATABASE=testing TESTING_DB_USERNAME=testing TESTING_DB_PASSWORD=secret \
vendor/bin/pestCI runs all three engines: SQLite catches a LIKE without an ESCAPE clause, PostgreSQL covers the ilike path and real jsonb columns (including sluggable’s per-locale slugs over them), and MySQL proves the like branch on a real server. PostgreSQL-only cases skip visibly when no engine is reachable, so a run that asserts nothing can’t report green.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.