Tes Situs Next.js dengan node:test dan Build Gate
Tes situs Next.js tanpa Jest: contract test node:test, prebuild gate seperti assertBlogParity, dan cara memutuskan apa yang dijaga tes dan gate.
Oleh Matthews WongDitinjau pada
Poin utama
Contract test memeriksa bahwa data atau fungsi murni Anda masih mematuhi sebuah aturan, dan test runner bawaan node:test sudah cukup untuk menulisnya. Build gate adalah skrip yang menggagalkan build ketika sebuah invarian rusak. Pakai tes untuk perilaku dan kontrak konten, dan gate untuk invarian murah yang tidak boleh sampai ke produksi.
Setelah pelajaran ini, Anda bisa
- Menulis contract test berbasis data dengan node:test dan menjalankannya dengan node --test.
- Menulis prebuild gate yang gagal dengan pesan yang menyebut cara memperbaikinya.
- Memutuskan apakah sebuah aturan masuk ke tes, gate, atau pengecekan live.
Ikuti diagramnya langkah demi langkah
Tekan Putar, atau melangkah dengan panah. Setiap langkah juga ditulis di bawah diagram.
Satu perubahan, tiga jaring pengaman
Perubahan data bertemu npm test, lalu prebuild gate, lalu build dengan assertBlogParity, dan terakhir agents:check di situs live, masing-masing gagal di tempat dan dengan pesan yang berbeda.
Tombol: ← → langkah · Home / End · K putar atau jeda
Empat pos pemeriksaan berdiri di antara sebuah edit dan pengunjung. Masing-masing murah, cepat, dan mengawasi hal yang berbeda.
Anda mengedit Dockerfile yang dikutip sebuah pelajaran. npm test mengimpor setiap pelajaran dan menjalankan satu pengecekan per pelajaran terhadap datanya.
Tebak langkah berikutnya Pelajaran masih mengutip baris Dockerfile yang lama. Apa yang menangkapnya lebih dulu? (Tes kutipan di npm test · Build produksi · Tidak ada, langsung rilis)Kutipan tidak lagi cocok dengan file, jadi sebuah assertion melempar error, tes gagal, dan proses keluar dengan kode 1. Pesannya menyuruh memperbarui pelajaran.
node --test tests/learn-content.test.mjsSetelah diperbaiki, prebuild menjalankan skrip Node biasa sebelum build dimulai. Salah satunya membandingkan file hasil generate dengan yang akan dihasilkan sumber saat ini.
node scripts/generate-blog-sources.mjs --checkTebak langkah berikutnya File sitasi hasil generate sudah usang. Apa yang dilakukan gate? (Build lanjut dengan sitasi lama · Keluar dengan kode 1 dan mencetak perintah yang harus dijalankan)Gate mencetak bahwa file usang beserta perintah untuk meregenerasinya, lalu keluar dengan kode 1. Build tidak pernah dimulai, jadi tidak ada yang usang yang bisa dirilis.
process.exit(1);Setelah diregenerasi, gate lolos. Build menegaskan paritas post saat merender sitemap, dan agents:check kemudian menguji situs yang berjalan lewat HTTP.
assertBlogParity();
Bisakah node:test menggantikan Jest untuk situs Next.js?
Untuk banyak situs, bisa. Node.js membawa test runner sendiri: Anda mengimpor test, atau describe dan it, dari node:test, dan sebuah tes lulus kecuali ia melempar error, mengembalikan promise yang ditolak, atau melaporkan error. Dokumentasi menandai runner ini stabil sejak Node 20. Anda menjalankannya dengan node --test, yang secara default mengambil file bernama seperti *.test.mjs, dan jika ada tes yang gagal, exit code proses adalah 1.
Baca selengkapnya (1)Tutup
Repo ini tidak punya dependensi framework tes. Tesnya ada di tests/ sebagai file .test.mjs, assertion berasal dari modul assert milik Node, dan satu npm script menjalankannya. Karena modul data TypeScript bukan JavaScript biasa, setiap file tes lebih dulu memuat resolver kecil, sehingga bisa mengimpor file lib yang asli, bukan salinan. Header tes mencatat bahwa ini membutuhkan Node 22.18 atau lebih baru.
Apa yang sebaiknya dijaga oleh sebuah tes?
Tes cocok untuk perilaku yang bisa digambarkan sebagai input dan output. Fungsi header Accept adalah contohnya: tabel string header dan jawaban yang diharapkan, satu tes dibuat per baris. Header browser, bintang-slash-bintang polos, text/markdown dengan q=0, dan header kosong adalah kasus yang sulit direproduksi manual tetapi nyaris gratis disimpan dalam tabel.
Baca selengkapnya (2)Tutup
Tes juga cocok untuk kontrak konten. Kursus /learn adalah data, jadi satu file tes melakukan loop ke setiap file pelajaran dan membuat pengecekan untuk masing-masing: slug sama dengan nama file, judul muat dalam batas SERP, kuis punya tiga pertanyaan, dan setiap kutipan kode harus masih muncul persis sama di file aslinya. Ubah Dockerfile dan pelajaran yang mengutipnya gagal sampai seseorang memperbaruinya. Itu deteksi drift yang tidak akan tertangkap mata reviewer.
Next.js juga menyediakan helper sendiri. Dokumentasi menjelaskan fungsi eksperimental unstable_doesProxyMatch di next/experimental/testing/server yang menegaskan apakah middleware akan berjalan untuk sebuah URL, sehingga perubahan matcher bisa dikunci oleh tes. Fungsinya bertanda eksperimental, jadi anggap opsional.
Lalu apa yang sebaiknya dijaga oleh build gate?
Gate menjaga invarian yang harus berlaku agar build layak dirilis, dan harus murah serta ringan dependensi. Skrip prebuild repo ini merangkai skrip Node kecil: satu memeriksa file sitasi hasil generate masih terbaru, satu memastikan setiap proyek punya penempatan di game, satu memastikan mesin 3D hanya dijangkau lewat batas lazy, dan satu memastikan pohon key terjemahan cocok. Masing-masing keluar dengan kode bukan nol dan pesan, yang menghentikan build.
Baca selengkapnya (2)Tutup
Gate juga bisa hidup di kode aplikasi. assertBlogParity melempar error jika registri post dan tabel metadatanya mencakup slug yang berbeda, dan juga jika tanggal yang ditampilkan kartu bertentangan dengan tanggal di structured data. Ia berjalan saat sitemap, feed, dan llms.txt dibangun, sehingga registri yang rusak menggagalkan build alih-alih merilis sitemap yang salah. Pengecekan tanggal itu ada karena membandingkan himpunan slug saja pernah membiarkan 168 post menyimpang.
Kenapa skrip biasa, bukan tes? Gate placement menyebutnya sendiri: image build memakai Node 20 yang tidak bisa mengimpor modul data TypeScript, jadi ia membaca slug dengan regex. Itu membuatnya bebas dependensi dan bisa berjalan di mana pun build berjalan. Tes yang membutuhkan Node lebih baru dan resolver tidak akan berjalan di sana.
Bagaimana memutuskan antara tes, gate, dan pengecekan live?
Ajukan tiga pertanyaan. Apakah rusaknya hal ini akan diam-diam merilis situs yang buruk? Jadikan gate, karena build yang gagal tidak bisa dirilis. Apakah ia butuh impor yang kaya, banyak kasus, atau penjelasan maksud? Tulis tes, tempat tabel dan loop terasa alami. Apakah ia bergantung pada perilaku HTTP yang nyata, seperti header yang benar-benar dikirim server terdeploy? Itu pengecekan live: tes unit sendiri menyatakannya, dan skrip terpisah, agents:check, menguji situs yang berjalan.
Baca selengkapnya (1)Tutup
Apa pun pilihan Anda, buat kegagalannya mengajarkan perbaikannya. Gate file usang mencetak nama file dan perintah persis untuk meregenerasinya. Pengecekan paritas mencetak selisih slug. Kegagalan yang menyebut apa yang harus dijalankan berikutnya mengubah build yang terblokir menjadi pekerjaan tiga puluh detik.
Konfigurasi nyata di balik pelajaran ini
Kutipan dari repositori yang membangun dan men-deploy situs ini, apa adanya.
"test": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --test tests/",
"agents:check": "node scripts/check-agent-readiness.mjs",
"prebuild": "node scripts/generate-blog-sources.mjs --check && node scripts/game/check-placements.mjs && node scripts/game/check-imports.mjs && node scripts/check-message-keys.mjs",
"sources:build": "node scripts/generate-blog-sources.mjs",Tiga jaring berbeda dalam empat baris. test menjalankan contract test dengan node --test, prebuild merangkai gate dengan && sehingga kegagalan pertama menghentikan semuanya, dan agents:check adalah pengujian sesuai permintaan untuk situs yang berjalan. Baris sources:build adalah perbaikan yang ditunjuk gate file usang.
if (CHECK_ONLY) {
if (next !== previous) {
console.error(
`FAIL ${path.relative(ROOT, OUT)} is stale — run: node scripts/generate-blog-sources.mjs`,
);
process.exit(1);
}Gate kesegaran dalam enam baris. Ia meregenerasi konten di memori, membandingkannya dengan file yang di-commit, dan jika berbeda mencetak file serta perintah perbaikannya sebelum keluar dengan kode 1. Skrip yang sama menulis file itu saat dijalankan tanpa flag.
/**
* Build-time guard: BLOG_POSTS_STATIC (which posts exist) and BLOG_META
* (their SEO metadata) must cover exactly the same slugs. Throws with the
* diff so a divergent build fails instead of shipping a broken sitemap.
*/
export function assertBlogParity(): void {Gate di dalam kode aplikasi. Ia dipanggil di tempat sitemap, feed, dan llms.txt dihasilkan, sehingga build tidak bisa selesai dengan dua registri yang saling berbeda. Error-nya mencantumkan selisih, bukan sekadar jumlah.
for (const [accept, expected] of cases) {
it(`${JSON.stringify(accept)} -> ${expected ? "markdown" : "html"}`, () => {
assert.equal(prefersMarkdown(accept), expected);
});
}Tes berbasis data: loop mengubah setiap baris tabel cases menjadi tes bernama sendiri. Menambah kasus header cukup satu baris data, dan kegagalan menyebut persis header yang rusak.
* The data modules are TypeScript and the build image runs Node 20, which
* cannot import them; the slugs are read with a regex instead. That is
* enough for a parity check and keeps this script dependency-free.Kenapa gate berupa skrip biasa. Ia harus berjalan di tempat build berjalan, dan lingkungan itu tidak bisa memuat data TypeScript, jadi ia menukar sedikit ketepatan dengan nol dependensi.
Kesalahan yang benar-benar membuang waktu
Gate yang lolos padahal datanya salah
Membandingkan himpunan slug saja membiarkan 168 tanggal post menyimpang, karena setiap nilai valid sendiri-sendiri. Saat dua sumber harus sepakat, periksa nilai yang mereka bagi, bukan sekadar keduanya ada.
Tes yang butuh Node lebih baru dari build
Contract test butuh Node 22.18 atau lebih baru, sedangkan image build memakai Node 20. Taruh apa pun yang harus ditegakkan build di skrip biasa yang bisa dijalankan Node 20, dan simpan tes berbasis resolver untuk npm test.
Pesan gagal yang tidak menyebut apa yang harus dilakukan
Exit 1 tanpa penjelasan membuat orang mencari-cari. Sebutkan file, aturan, dan perintah yang memperbaikinya, seperti yang dilakukan gate file usang.
Memalsukan sesuatu yang bisa dijawab file asli
Tes dengan salinan Dockerfile tidak bisa menyadari Dockerfile berubah. Baca file aslinya dan bandingkan, seperti tes kutipan.
| Aspek | Contract test | Build gate | Pengecekan live |
|---|---|---|---|
| Dijalankan oleh | npm test, sesuai kebutuhan | prebuild atau build itu sendiri | agents:check, sesuai kebutuhan |
| Menjaga | Perilaku, kontrak data, drift | Invarian yang tidak boleh dirilis rusak | Jawaban server asli lewat HTTP |
| Membutuhkan | Resolver TypeScript, Node 22.18+ | Hanya Node, berjalan di build | Situs yang berjalan |
Lakukan di komputer Anda sendiri
Tulis satu tes berbasis data dan satu gate untuk sesuatu di proyek Anda sendiri.
Buat tests/slug.test.mjs. Taruh kasus di sebuah array dan biarkan loop membuat satu tes bernama per kasus.
import assert from "node:assert/strict"; import { describe, it } from "node:test"; const cases = [["Hello World", "hello-world"], [" a b ", "a-b"]]; describe("slugify", () => { for (const [input, expected] of cases) { it(`${JSON.stringify(input)} -> ${expected}`, () => { assert.equal(slugify(input), expected); }); } });Jalankan dengan node --test, lalu rusak satu nilai yang diharapkan dan baca kegagalannya. Pastikan exit code-nya 1.
node --test tests/ echo $?Tulis sebuah gate: skrip yang membangun ulang file hasil generate di memori, membandingkannya dengan yang di-commit, dan keluar dengan kode 1 beserta perintah perbaikan saat berbeda.
Rangkai gate itu di skrip prebuild agar berjalan sebelum setiap build, lalu ubah sumbernya tanpa regenerasi dan lihat build berhenti.
"prebuild": "node scripts/check-generated.mjs"
Tiga pertanyaan sebelum lanjut
Pilih jawaban untuk melihat kenapa benar atau salah. Tidak ada yang disimpan atau dinilai, jadi tebak saja dengan bebas.
Jawab dalam hati, lalu cek
1.Kenapa assertBlogParity mendapat pengecekan tanggal selain membandingkan slug?Lihat jawabannya
Jawaban:Mencocokkan himpunan slug tidak cukup: 168 post menampilkan tanggal yang bertentangan dengan tanggal di structured data, dan setiap nilai valid sendiri-sendiri. Gate sebaiknya membandingkan nilai bersama yang bisa berbeda.
2.Sebuah tes gagal ketika kutipan tidak lagi cocok dengan filenya. Apakah itu bug di tes?Lihat jawabannya
Jawaban:Bukan, itulah tujuannya. Pelajaran mengutip file asli, jadi saat file berubah pelajaran itu usang, dan kegagalan memaksa seseorang memperbaruinya.
3.Dari tiga jaring itu, mana yang Anda pakai untuk header yang benar-benar dikirim server terdeploy?Lihat jawabannya
Jawaban:Pengecekan live. Tes unit hanya menjalankan kode, jadi repo ini menyimpan skrip terpisah, agents:check, yang membuat request HTTP nyata ke situs yang berjalan.
Bahasan mendalam terkait di situs ini
Sumber
Sumber primer yang diperiksa pada tanggal riset. Ikuti tautannya untuk versi terbaru.