Anatomi Schema Drift pada Inertia.js
Inertia.js meniadakan lapisan API client formal seperti REST endpoints mandiri atau GraphQL schemas. Controller backend langsung meneruskan data ke page component frontend melalui payload props. Kemudahan ini memiliki konsekuensi: batasan tipe (type boundary) antara backend dan frontend menjadi rentan terhadap schema drift.
Schema drift terjadi ketika backend mengubah representasi data tanpa pembaruan sinkron pada frontend, atau sebaliknya. Contoh kasus:
- Backend mengubah nama atribut Eloquent dari
user_idmenjadiauthor_idatau memformat ulang strukturcreated_at. - Frontend TypeScript interface masih mengharapkan tipe lama.
- Uji backend standar (HTTP Feature Test) hanya memeriksa
$response->assertOk()dan lolos karena controller sukses merender halaman. - Frontend type-checking (
tsc --noEmit) lolos saat build CI karena static mock atau interface yang ditulis manual tidak pernah divalidasi silang terhadap payload nyata controller.
Hasil akhirnya adalah TypeError: Cannot read properties of undefined di browser pengguna saat payload yang dikirim backend tidak sesuai ekspektasi frontend.
Verifikasi Struktur Props dengan assertInertia
Laravel menyediakan integrasi testing bawaan untuk Inertia melalui kelas Inertia\Testing\AssertableInertia. Assertion ini memungkinkan pengujian payload tanpa harus mengeksekusi browser headless.
use Inertia\Testing\AssertableInertia as Assert;
test('halaman edit artikel memuat struktur props yang valid', function () {
$article = Article::factory()->create([
'title' => 'Panduan Testing Inertia',
'is_published' => true,
]);
$this->get(route('articles.edit', $article))
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('Articles/Edit')
->has('article', fn (Assert $prop) => $prop
->where('id', $article->id)
->where('title', 'Panduan Testing Inertia')
->whereType('is_published', 'boolean')
->whereType('tags', 'array')
->has('author', fn (Assert $author) => $author
->hasAll(['id', 'name', 'email'])
->whereType('id', 'integer')
)
)
);
});Gunakan whereType secara konsisten untuk memastikan nilai null yang tidak terduga atau konversi tipe database (seperti casting integer ke string di database engine tertentu) dapat ditangkap sebelum masuk tahap staging.
Menangani Lazy Data Evaluation
Inertia mendukung Inertia::lazy() untuk mengeksekusi props hanya saat diminta secara eksplisit via partial reloads. Pengujian kontrak harus memisahkan verifikasi initial visit dari partial visit.
Controller contoh:
return Inertia::render('Articles/Show', [
'article' => $article,
'metrics' => Inertia::lazy(fn () => [
'views_count' => $article->views()->count(),
'retention_rate' => 0.85,
]),
]);Uji skenario default dan lazy request menggunakan header X-Inertia-Partial-Data:
// 1. Initial visit: pastikan lazy prop tidak dieksekusi
$this->get(route('articles.show', $article))
->assertInertia(fn (Assert $page) => $page
->component('Articles/Show')
->has('article.id')
->missing('metrics')
);
// 2. Partial reload visit: verifikasi skema lazy prop saat diminta
$this->withHeaders([
'X-Inertia' => 'true',
'X-Inertia-Partial-Component' => 'Articles/Show',
'X-Inertia-Partial-Data' => 'metrics',
])->get(route('articles.show', $article))
->assertInertia(fn (Assert $page) => $page
->has('metrics', fn (Assert $metrics) => $metrics
->whereType('views_count', 'integer')
->whereType('retention_rate', 'double')
)
);Sinkronisasi Type Definition Otomatis
Menulis interface TypeScript secara manual di frontend menciptakan duplikasi kontrak yang rentan kadaluarsa. Solusi terbaik adalah menstandarisasi payload menggunakan Data Transfer Object (DTO) di backend, lalu mengonversinya ke TypeScript secara otomatis.
Gunakan pustaka seperti spatie/laravel-data yang terintegrasi dengan spatie/typescript-transformer:
use Spatie\LaravelData\Data;
class ArticleData extends Data
{
public function __construct(
public int $id,
public string $title,
public bool $is_published,
/** @var array<int, string> */
public array $tags,
) {}
}Saat perintah php artisan typescript:transform dijalankan, file TypeScript dihasilkan secara deterministik:
export type ArticleData = {
id: number;
title: string;
is_published: boolean;
tags: Array<string>;
};Frontend page mengimpor tipe tersebut langsung sebagai props:
import { PageProps } from '@/types';
import { ArticleData } from '@/types/generated';
interface Props extends PageProps {
article: ArticleData;
}
export default function Edit({ article }: Props) {
return <div>{article.title}</div>;
}Pipeline CI: Otomasi Deteksi Regresi
Di Continuous Integration (CI), rangkaian pemeriksaan harus memverifikasi bahwa kode yang dihasilkan selalu sinkron dengan status repositori dan tidak ada perubahan skema yang melanggar tipe frontend.
Workflow step pada GitHub Actions:
jobs:
contract-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP & Composer
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, pdo, sqlite
- name: Install Backend Dependencies
run: composer install --prefer-dist --no-interaction --no-progress
- name: Generate TypeScript Definitions
run: php artisan typescript:transform
- name: Check for Uncommitted Schema Changes
run: git diff --exit-code resources/js/types/generated.d.ts
- name: Run Backend Contract Tests
run: php artisan test --filter=AssertInertia
- name: Setup Node.js & Frontend Type Check
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Frontend Dependencies
run: npm ci
- name: Verify Frontend Types Against Payload Contract
run: npm run type-check # npx tsc --noEmitMekanisme Penanganan Kegagalan
- Jika backend engineer mengubah DTO tanpa menjalankan transformer lokal, CI gagal pada langkah
git diff --exit-code. - Jika backend engineer memperbarui DTO dan me-regenerate types, tetapi frontend belum menyesuaikan kode komponen, CI lolos di langkah backend namun langsung gagal pada
npm run type-check. - Jika controller mengembalikan props yang tidak sesuai dengan assertion backend,
php artisan testgagal langsung pada level HTTP/Inertia assertion.
Peringatan Efisiensi: Hindari melakukan assert menyeluruh pada prop non-kritis seperti meta token atau CSRF token di setiap test file. Batasi validasi kontrak mendalam pada data bisnis dan DTO inti untuk menjaga performa suite test tetap optimal.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!