Pengujian Sistem Kasir Laravel
Diterbitkan 16 September 2026
Pada tahap kesebelas kita akan melakukan pengujian Sistem Kasir Laravel sebelum aplikasi dipindahkan ke server production.
Sistem kasir tidak cukup diuji hanya dengan membuka halaman dan memastikan tidak ada error.
Bagian penting seperti:
login
hak akses
harga produk
stok
penjualan
pembelian
pembatalan transaksi
laporan
harus diuji secara terstruktur.
Pengujian penting karena kesalahan kecil dapat menyebabkan:
stok salah
total transaksi salah
transaksi ganda
kasir membuka halaman admin
pembatalan mengembalikan stok dua kali
laporan menghitung transaksi cancelled
Pada tahap ini kita akan menggunakan dua pendekatan:
automated test
manual test
Automated test menggunakan fitur testing Laravel.
Manual test digunakan untuk memeriksa alur antarmuka, print nota, filter, dan perilaku aplikasi secara keseluruhan.
Hasil yang Akan Dibuat
Pada tahap ini kita akan membuat test untuk:
- user yang belum login;
- hak akses admin dan kasir;
- produk;
- transaksi penjualan;
- validasi stok;
- harga dari database;
- pengurangan stok;
- stock movement;
- pembayaran kurang;
- pembatalan transaksi;
- proteksi pembatalan ganda;
- transaksi pembelian;
- penambahan stok;
- pembatalan pembelian;
- laporan hanya menghitung transaksi paid.
File test utama:
tests/Feature/AuthAccessTest.php
tests/Feature/ProductTest.php
tests/Feature/SaleTest.php
tests/Feature/PurchaseTest.php
tests/Feature/ReportTest.php
Persiapan
Pastikan Tahap 01 sampai Tahap 10 sudah selesai.
Masuk ke folder project:
cd kasir-laravel
Pastikan aplikasi masih berjalan normal.
Sebelum testing, cek:
php artisan about
Kemudian:
php artisan test
Jika project masih menggunakan test bawaan Laravel, hasilnya seharusnya tetap dapat dijalankan.
1. Jangan Menguji Menggunakan Database Production
Automated test tidak boleh menggunakan database production.
Laravel biasanya menggunakan environment:
testing
Kita akan menggunakan:
RefreshDatabase
agar database test dapat disiapkan ulang.
Untuk project sederhana, SQLite in-memory dapat digunakan jika seluruh fitur database yang dipakai kompatibel.
Alternatifnya gunakan database MySQL khusus testing.
2. Konfigurasi Testing dengan SQLite
Buka:
phpunit.xml
Pastikan environment testing mempunyai:
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
Contoh bagian environment:
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
</php>
Jika project menggunakan MySQL testing, bagian database dapat disesuaikan.
3. Mengapa Menggunakan RefreshDatabase?
Test class akan menggunakan:
use RefreshDatabase;
Trait ini membantu menyiapkan database untuk setiap test.
Dengan demikian satu test tidak mudah mengganggu test lainnya.
4. Membuat AuthAccessTest
Jalankan:
php artisan make:test AuthAccessTest
File:
tests/Feature/AuthAccessTest.php
Isi:
<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AuthAccessTest extends TestCase
{
use RefreshDatabase;
public function test_guest_cannot_open_dashboard(): void
{
$response = $this->get('/dashboard');
$response->assertRedirect('/login');
}
public function test_admin_can_open_master_data(): void
{
$admin = User::factory()->create([
'role' => 'admin',
'is_active' => true,
]);
$response = $this
->actingAs($admin)
->get('/categories');
$response->assertOk();
}
public function test_cashier_cannot_open_admin_master_data(): void
{
$cashier = User::factory()->create([
'role' => 'cashier',
'is_active' => true,
]);
$response = $this
->actingAs($cashier)
->get('/categories');
$response->assertForbidden();
}
public function test_cashier_can_open_sales_page(): void
{
$cashier = User::factory()->create([
'role' => 'cashier',
'is_active' => true,
]);
$response = $this
->actingAs($cashier)
->get('/sales');
$response->assertOk();
}
}
5. Jika Middleware Role Menggunakan Redirect
Pada Tahap 01 middleware role dapat saja dibuat dengan:
403
atau redirect.
Jika implementasi middleware menggunakan redirect, sesuaikan assertion.
Contoh:
$response->assertRedirect('/dashboard');
Gunakan assertion sesuai implementasi project.
6. Factory User dan Kolom Role
Pastikan factory User tidak bermasalah dengan kolom:
role
is_active
Kita menimpa nilainya pada test:
User::factory()->create([
'role' => 'admin',
'is_active' => true,
]);
7. Membuat ProductTest
Jalankan:
php artisan make:test ProductTest
Isi:
<?php
namespace Tests\Feature;
use App\Models\Category;
use App\Models\Product;
use App\Models\Unit;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ProductTest extends TestCase
{
use RefreshDatabase;
private function admin(): User
{
return User::factory()->create([
'role' => 'admin',
'is_active' => true,
]);
}
public function test_admin_can_create_product(): void
{
$admin = $this->admin();
$category = Category::create([
'name' => 'Minuman',
]);
$unit = Unit::create([
'name' => 'Botol',
'symbol' => 'btl',
]);
$response = $this
->actingAs($admin)
->post('/products', [
'category_id' => $category->id,
'unit_id' => $unit->id,
'sku' => 'brg-100',
'name' => 'Produk Test',
'purchase_price' => 5000,
'selling_price' => 7000,
'minimum_stock' => 5,
'is_active' => 1,
]);
$response->assertRedirect('/products');
$this->assertDatabaseHas('products', [
'sku' => 'BRG-100',
'name' => 'Produk Test',
'stock' => 0,
]);
}
public function test_product_stock_cannot_be_set_from_create_request(): void
{
$admin = $this->admin();
$response = $this
->actingAs($admin)
->post('/products', [
'sku' => 'BRG-101',
'name' => 'Produk Stock Test',
'purchase_price' => 1000,
'selling_price' => 2000,
'minimum_stock' => 1,
'stock' => 999,
'is_active' => 1,
]);
$response->assertRedirect('/products');
$this->assertDatabaseHas('products', [
'sku' => 'BRG-101',
'stock' => 0,
]);
}
public function test_duplicate_sku_is_rejected(): void
{
$admin = $this->admin();
Product::create([
'sku' => 'BRG-001',
'name' => 'Produk Lama',
'purchase_price' => 1000,
'selling_price' => 2000,
'stock' => 0,
'minimum_stock' => 0,
'is_active' => true,
]);
$response = $this
->actingAs($admin)
->post('/products', [
'sku' => 'BRG-001',
'name' => 'Produk Baru',
'purchase_price' => 1000,
'selling_price' => 2500,
'minimum_stock' => 0,
'is_active' => 1,
]);
$response->assertSessionHasErrors('sku');
}
}
8. Test Penting Produk
Test kedua memastikan request seperti:
stock=999
tidak dapat memanipulasi saldo produk.
Controller tetap membuat:
stock = 0
9. Membuat SaleTest
Jalankan:
php artisan make:test SaleTest
Isi:
<?php
namespace Tests\Feature;
use App\Models\Product;
use App\Models\Sale;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class SaleTest extends TestCase
{
use RefreshDatabase;
private function cashier(): User
{
return User::factory()->create([
'role' => 'cashier',
'is_active' => true,
]);
}
private function admin(): User
{
return User::factory()->create([
'role' => 'admin',
'is_active' => true,
]);
}
private function product(
int $stock = 10,
float $price = 5000
): Product {
return Product::create([
'sku' => 'BRG-' . uniqid(),
'name' => 'Produk Test',
'purchase_price' => 3000,
'selling_price' => $price,
'stock' => $stock,
'minimum_stock' => 2,
'is_active' => true,
]);
}
public function test_sale_reduces_stock(): void
{
$cashier = $this->cashier();
$product = $this->product(stock: 10);
$response = $this
->actingAs($cashier)
->post('/sales', [
'items' => [
[
'product_id' => $product->id,
'quantity' => 2,
],
],
'discount' => 0,
'paid_amount' => 10000,
]);
$response->assertRedirect();
$this->assertDatabaseHas('products', [
'id' => $product->id,
'stock' => 8,
]);
$this->assertDatabaseHas('stock_movements', [
'product_id' => $product->id,
'type' => 'sale',
'quantity_out' => 2,
]);
}
public function test_server_uses_database_price(): void
{
$cashier = $this->cashier();
$product = $this->product(
stock: 10,
price: 5000
);
$this
->actingAs($cashier)
->post('/sales', [
'items' => [
[
'product_id' => $product->id,
'quantity' => 2,
'price' => 1,
'subtotal' => 2,
],
],
'grand_total' => 2,
'discount' => 0,
'paid_amount' => 10000,
]);
$sale = Sale::latest('id')->first();
$this->assertNotNull($sale);
$this->assertEquals(
10000,
(float) $sale->grand_total
);
$detail = $sale->details()->first();
$this->assertEquals(
5000,
(float) $detail->price
);
}
public function test_sale_fails_when_stock_is_insufficient(): void
{
$cashier = $this->cashier();
$product = $this->product(stock: 1);
$response = $this
->actingAs($cashier)
->post('/sales', [
'items' => [
[
'product_id' => $product->id,
'quantity' => 2,
],
],
'discount' => 0,
'paid_amount' => 10000,
]);
$response->assertSessionHasErrors('items');
$this->assertDatabaseCount('sales', 0);
$this->assertDatabaseHas('products', [
'id' => $product->id,
'stock' => 1,
]);
}
public function test_sale_fails_when_payment_is_less_than_total(): void
{
$cashier = $this->cashier();
$product = $this->product(
stock: 10,
price: 5000
);
$response = $this
->actingAs($cashier)
->post('/sales', [
'items' => [
[
'product_id' => $product->id,
'quantity' => 2,
],
],
'discount' => 0,
'paid_amount' => 5000,
]);
$response->assertSessionHasErrors('paid_amount');
$this->assertDatabaseCount('sales', 0);
$this->assertDatabaseHas('products', [
'id' => $product->id,
'stock' => 10,
]);
}
public function test_cancelled_sale_restores_stock_once(): void
{
$cashier = $this->cashier();
$admin = $this->admin();
$product = $this->product(stock: 10);
$this
->actingAs($cashier)
->post('/sales', [
'items' => [
[
'product_id' => $product->id,
'quantity' => 2,
],
],
'discount' => 0,
'paid_amount' => 10000,
]);
$sale = Sale::latest('id')->first();
$this->assertDatabaseHas('products', [
'id' => $product->id,
'stock' => 8,
]);
$this
->actingAs($admin)
->post("/sales/{$sale->id}/cancel");
$this->assertDatabaseHas('products', [
'id' => $product->id,
'stock' => 10,
]);
$this->assertDatabaseHas('sales', [
'id' => $sale->id,
'status' => 'cancelled',
]);
$secondResponse = $this
->actingAs($admin)
->post("/sales/{$sale->id}/cancel");
$secondResponse->assertSessionHasErrors('cancel');
$this->assertDatabaseHas('products', [
'id' => $product->id,
'stock' => 10,
]);
$this->assertDatabaseCount(
'stock_movements',
2
);
}
}
10. Mengapa Test Harga Sangat Penting?
Test:
server_uses_database_price
mencoba mengirim:
price = 1
subtotal = 2
grand_total = 2
Server harus mengabaikan nilai tersebut.
Jika database price:
5000
dan quantity:
2
grand total tetap:
10000
11. Menguji Rollback Secara Tidak Langsung
Pada test stok tidak cukup:
sales count = 0
stock tetap
Ini membuktikan transaksi gagal tanpa meninggalkan header transaksi.
Test pembayaran kurang juga memastikan perubahan tidak tersimpan.
12. Membuat PurchaseTest
Jalankan:
php artisan make:test PurchaseTest
Isi:
<?php
namespace Tests\Feature;
use App\Models\Product;
use App\Models\Purchase;
use App\Models\Supplier;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PurchaseTest extends TestCase
{
use RefreshDatabase;
private function admin(): User
{
return User::factory()->create([
'role' => 'admin',
'is_active' => true,
]);
}
private function product(int $stock = 5): Product
{
return Product::create([
'sku' => 'BRG-' . uniqid(),
'name' => 'Produk Test',
'purchase_price' => 2000,
'selling_price' => 4000,
'stock' => $stock,
'minimum_stock' => 2,
'is_active' => true,
]);
}
public function test_purchase_adds_stock(): void
{
$admin = $this->admin();
$product = $this->product(stock: 5);
$supplier = Supplier::create([
'name' => 'Supplier Test',
]);
$response = $this
->actingAs($admin)
->post('/purchases', [
'supplier_id' => $supplier->id,
'purchase_date' => now()
->format('Y-m-d H:i:s'),
'items' => [
[
'product_id' => $product->id,
'quantity' => 10,
'price' => 2500,
],
],
]);
$response->assertRedirect();
$this->assertDatabaseHas('products', [
'id' => $product->id,
'stock' => 15,
]);
$this->assertDatabaseHas('stock_movements', [
'product_id' => $product->id,
'type' => 'purchase',
'quantity_in' => 10,
]);
}
public function test_purchase_updates_latest_purchase_price(): void
{
$admin = $this->admin();
$product = $this->product();
$this
->actingAs($admin)
->post('/purchases', [
'purchase_date' => now()
->format('Y-m-d H:i:s'),
'items' => [
[
'product_id' => $product->id,
'quantity' => 2,
'price' => 2750,
],
],
]);
$this->assertDatabaseHas('products', [
'id' => $product->id,
'purchase_price' => 2750,
]);
}
public function test_cancel_purchase_reduces_stock(): void
{
$admin = $this->admin();
$product = $this->product(stock: 5);
$this
->actingAs($admin)
->post('/purchases', [
'purchase_date' => now()
->format('Y-m-d H:i:s'),
'items' => [
[
'product_id' => $product->id,
'quantity' => 10,
'price' => 2500,
],
],
]);
$purchase = Purchase::latest('id')->first();
$this->assertDatabaseHas('products', [
'id' => $product->id,
'stock' => 15,
]);
$this
->actingAs($admin)
->post(
"/purchases/{$purchase->id}/cancel"
);
$this->assertDatabaseHas('products', [
'id' => $product->id,
'stock' => 5,
]);
$this->assertDatabaseHas('purchases', [
'id' => $purchase->id,
'status' => 'cancelled',
]);
}
public function test_purchase_cancel_is_rejected_if_stock_is_not_enough(): void
{
$admin = $this->admin();
$product = $this->product(stock: 0);
$this
->actingAs($admin)
->post('/purchases', [
'purchase_date' => now()
->format('Y-m-d H:i:s'),
'items' => [
[
'product_id' => $product->id,
'quantity' => 10,
'price' => 2500,
],
],
]);
$purchase = Purchase::latest('id')->first();
$product->update([
'stock' => 3,
]);
$response = $this
->actingAs($admin)
->post(
"/purchases/{$purchase->id}/cancel"
);
$response->assertSessionHasErrors('cancel');
$this->assertDatabaseHas('purchases', [
'id' => $purchase->id,
'status' => 'received',
]);
}
}
13. Catatan Test Manipulasi Stock pada Purchase
Pada test terakhir kita mengubah:
$product->update([
'stock' => 3,
]);
secara langsung hanya untuk mensimulasikan kondisi bahwa stok sudah keluar setelah pembelian.
Pada aplikasi normal perubahan tersebut dilakukan oleh penjualan.
Dalam production jangan menggunakan cara langsung tersebut.
14. Membuat ReportTest
Jalankan:
php artisan make:test ReportTest
Isi:
<?php
namespace Tests\Feature;
use App\Models\Sale;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ReportTest extends TestCase
{
use RefreshDatabase;
public function test_report_revenue_only_counts_paid_sales(): void
{
$admin = User::factory()->create([
'role' => 'admin',
'is_active' => true,
]);
Sale::create([
'invoice_number' => 'INV-PAID',
'user_id' => $admin->id,
'sale_date' => now(),
'subtotal' => 100000,
'discount' => 0,
'grand_total' => 100000,
'paid_amount' => 100000,
'change_amount' => 0,
'status' => 'paid',
]);
Sale::create([
'invoice_number' => 'INV-CANCELLED',
'user_id' => $admin->id,
'sale_date' => now(),
'subtotal' => 50000,
'discount' => 0,
'grand_total' => 50000,
'paid_amount' => 50000,
'change_amount' => 0,
'status' => 'cancelled',
]);
$response = $this
->actingAs($admin)
->get('/reports/sales?status=');
$response->assertOk();
$response->assertViewHas(
'totalRevenue',
function ($value) {
return (float) $value === 100000.0;
}
);
}
}
15. Menjalankan Semua Test
Jalankan:
php artisan test
Laravel menjalankan seluruh test.
Hasil ideal:
PASS
untuk semua test.
16. Menjalankan Satu File Test
Contoh:
php artisan test tests/Feature/SaleTest.php
17. Menjalankan Test Berdasarkan Nama
Contoh:
php artisan test --filter=sale_reduces_stock
Ini berguna ketika sedang memperbaiki satu fitur.
18. Jika Test Gagal
Baca bagian:
FAIL
dan error yang diberikan.
Jangan hanya mengubah assertion agar test lolos.
Pastikan behavior aplikasi memang benar.
19. Manual Test Login
Uji:
[ ] login admin berhasil
[ ] login kasir berhasil
[ ] password salah ditolak
[ ] user tidak aktif ditolak
[ ] logout berhasil
20. Manual Test Role
Admin:
[ ] dapat membuka master data
[ ] dapat membuka produk
[ ] dapat membuka pembelian
[ ] dapat membuka stok
[ ] dapat membuka laporan
[ ] dapat membatalkan transaksi
Kasir:
[ ] dapat membuka dashboard
[ ] dapat membuka penjualan
[ ] dapat membuat penjualan
[ ] tidak dapat membuka master data admin
[ ] tidak dapat membuka pembelian
[ ] tidak dapat membuka adjustment stok
[ ] tidak dapat membuka laporan admin
[ ] tidak dapat cancel transaksi
21. Manual Test CRUD Master
Kategori:
[ ] create
[ ] edit
[ ] search
[ ] unique
[ ] delete aman
Satuan:
[ ] create
[ ] edit
[ ] search
[ ] delete aman
Pelanggan:
[ ] create
[ ] edit
[ ] search
[ ] delete
Supplier:
[ ] create
[ ] edit
[ ] search
[ ] delete
22. Manual Test Produk
Uji:
[ ] SKU unique
[ ] SKU uppercase
[ ] kategori valid
[ ] satuan valid
[ ] harga negatif ditolak
[ ] stock awal 0
[ ] stock tidak dapat diedit
[ ] produk dapat nonaktif
[ ] produk dengan histori tidak dapat dihapus
23. Manual Test Penjualan
Uji transaksi normal:
[ ] produk tampil
[ ] pencarian bekerja
[ ] cart bekerja
[ ] qty bekerja
[ ] subtotal tampil
[ ] diskon bekerja
[ ] pembayaran bekerja
[ ] kembalian benar
[ ] sale tersimpan
[ ] detail tersimpan
[ ] stock berkurang
[ ] movement dibuat
24. Manual Test Penjualan Negatif
Uji:
[ ] cart kosong ditolak
[ ] quantity 0 ditolak
[ ] quantity melebihi stock ditolak
[ ] produk inactive ditolak
[ ] pembayaran kurang ditolak
[ ] diskon > subtotal ditolak
[ ] harga browser tidak dipercaya
25. Manual Test Cancel Sale
Uji:
[ ] admin dapat cancel
[ ] kasir tidak dapat cancel
[ ] status berubah cancelled
[ ] stock kembali
[ ] movement reversal dibuat
[ ] cancel kedua ditolak
26. Manual Test Pembelian
Uji:
[ ] supplier dapat dipilih
[ ] item multi produk
[ ] quantity valid
[ ] harga beli valid
[ ] total benar
[ ] purchase tersimpan
[ ] detail tersimpan
[ ] stock bertambah
[ ] purchase_price diperbarui
[ ] movement dibuat
27. Manual Test Cancel Purchase
Uji:
[ ] cancel normal bekerja
[ ] stock dikurangi kembali
[ ] movement reversal dibuat
[ ] cancel kedua ditolak
[ ] cancel ditolak jika stock tidak cukup
28. Manual Test Kartu Stok
Uji:
[ ] initial tampil
[ ] sale tampil
[ ] sale_cancelled tampil
[ ] purchase tampil
[ ] purchase_cancelled tampil
[ ] adjustment tampil
[ ] saldo berjalan benar
[ ] filter type benar
[ ] filter tanggal benar
29. Manual Test Adjustment
Uji:
[ ] stock naik
[ ] stock turun
[ ] movement dibuat
[ ] notes wajib
[ ] nilai sama ditolak
[ ] negatif ditolak
30. Manual Test Dashboard
Uji:
[ ] omzet paid saja
[ ] cancelled tidak dihitung
[ ] jumlah transaksi benar
[ ] produk aktif benar
[ ] stok habis benar
[ ] low stock benar
[ ] best seller benar
31. Manual Test Laporan
Uji:
[ ] default awal bulan
[ ] tanggal valid
[ ] tanggal salah ditolak
[ ] filter paid
[ ] filter cancelled
[ ] filter semua
[ ] filter kasir
[ ] search invoice
[ ] pagination
[ ] omzet tidak menghitung cancelled
32. Manual Test Nota
Uji:
[ ] 58mm
[ ] 80mm
[ ] data toko
[ ] invoice
[ ] kasir
[ ] pelanggan
[ ] item
[ ] harga historis
[ ] total
[ ] bayar
[ ] kembalian
[ ] cancelled diberi tanda
[ ] toolbar tidak tercetak
33. Menguji Konsistensi Stock
Pilih satu produk.
Hitung:
$movementBalance =
$product->stockMovements()->sum('quantity_in')
-
$product->stockMovements()->sum('quantity_out');
Bandingkan:
$product->stock;
Harus sama.
34. Membuat Pemeriksaan Cepat via Tinker
Jalankan:
php artisan tinker
Kemudian:
use App\Models\Product;
Cek produk yang tidak konsisten:
Product::all()->filter(function ($product) {
$movementBalance =
$product->stockMovements()->sum('quantity_in')
-
$product->stockMovements()->sum('quantity_out');
return (int) $product->stock
!==
(int) $movementBalance;
});
Hasil ideal:
collection kosong
35. Menguji Status Production Sebelum Deploy
Pastikan tidak ada:
dd()
dump()
ray()
yang tertinggal pada controller.
Cari secara manual di project.
36. Cek Route
Jalankan:
php artisan route:list
Pastikan tidak ada route admin yang lupa menggunakan middleware role.
37. Cek Migration
Jalankan:
php artisan migrate:status
Semua migration harus:
Ran
38. Cek Config
Jalankan:
php artisan config:clear
php artisan cache:clear
php artisan view:clear
php artisan route:clear
Kemudian test ulang aplikasi.
39. Jangan Mengandalkan Testing Otomatis Saja
Automated test sangat membantu untuk logic.
Namun beberapa hal tetap harus diuji manual:
layout
mobile responsive
printer thermal
browser print
menu
flash message
JavaScript cart
UX
40. Browser yang Perlu Diuji
Minimal coba:
Chrome
Edge
Jika pengguna target menggunakan browser lain, lakukan pengujian tambahan.
41. Uji Resolusi Desktop
Karena POS umumnya digunakan desktop, uji minimal pada:
1366 × 768
1920 × 1080
Pastikan keranjang masih mudah digunakan.
42. Uji Mobile Secukupnya
Walaupun kasir biasanya desktop, halaman admin sebaiknya tetap dapat dibuka dari mobile.
Periksa:
table responsive
form
navigation
button
43. Uji Printer Sebelum Production
Jika aplikasi akan benar-benar dipakai kasir, jangan hanya mengandalkan print preview.
Cetak ke printer thermal nyata.
Periksa:
lebar
margin
font
potongan kertas
kecepatan
44. Uji Data Besar Sederhana
Tambahkan banyak data produk dan transaksi.
Pastikan:
pagination tetap bekerja
dashboard tetap wajar
laporan tidak terlalu lambat
Untuk aplikasi skala kecil, query tutorial ini masih cukup sederhana.
45. Uji Backup Database
Sebelum production, lakukan satu simulasi:
backup database
restore database
Pastikan backup benar-benar dapat digunakan.
Backup yang tidak pernah diuji restore belum tentu aman.
46. Checklist Akhir Sebelum Deploy
Pastikan:
[ ] login aman
[ ] role benar
[ ] CRUD bekerja
[ ] produk bekerja
[ ] penjualan bekerja
[ ] pembelian bekerja
[ ] stock konsisten
[ ] cancel aman
[ ] kartu stok benar
[ ] adjustment benar
[ ] dashboard benar
[ ] laporan benar
[ ] nota benar
[ ] automated test lulus
[ ] manual test selesai
[ ] tidak ada debug code
[ ] backup siap
47. Kesalahan Umum: Test Hanya Happy Path
Jangan hanya menguji transaksi berhasil.
Uji juga:
stok tidak cukup
harga dimanipulasi
pembayaran kurang
produk inactive
cancel dua kali
hak akses salah
48. Kesalahan Umum: Menggunakan Database Asli
Jangan menjalankan test otomatis pada database production.
Gunakan database testing.
49. Kesalahan Umum: Test Lulus tetapi UI Belum Diuji
Automated test tidak menggantikan pengecekan UI dan printer.
50. Kesalahan Umum: Tidak Menguji Cancel
Fitur cancel sangat berisiko karena dapat mengubah stok kembali.
Test pembatalan wajib.
51. Kesalahan Umum: Tidak Menguji Manipulasi Harga
Pastikan server tidak mempercayai nilai:
price
subtotal
grand_total
dari request penjualan.
52. Kesalahan Umum: Laporan Menghitung Cancelled
Test khusus laporan perlu memastikan omzet paid saja.
53. Pengembangan Testing Selanjutnya
Test dapat dikembangkan menjadi:
unit test
browser test
parallel test
CI/CD
GitHub Actions
coverage
performance test
security test
Untuk tutorial dasar, feature test di atas sudah memberikan fondasi yang baik.
54. Checkpoint Hasil
Setelah Tahap 11:
Login : selesai
Role admin/kasir : selesai
Database : selesai
Model dan relasi : selesai
Seeder : selesai
CRUD master : selesai
CRUD produk : selesai
Penjualan : selesai
Pembelian : selesai
Kartu stok : selesai
Adjustment : selesai
Dashboard : selesai
Laporan : selesai
Nota : selesai
Feature test : selesai
Manual test : selesai
Deploy : tahap terakhir
55. Ringkasan
Pada tahap kesebelas kita telah menambahkan pengujian untuk bagian terpenting sistem kasir.
Automated test mencakup:
auth
role
produk
penjualan
stok
harga server-side
pembayaran
cancel sale
pembelian
cancel purchase
laporan
Manual test melengkapi bagian yang sulit diverifikasi hanya dengan feature test:
JavaScript POS
layout
responsive
print thermal
filter
UX
Sebelum deploy, pastikan:
php artisan test
memberikan hasil yang sesuai dan checklist manual sudah selesai.
Tahap terakhir akan membahas deployment sistem kasir Laravel ke hosting/server production.
Slug lama pengujian-dan-deploy akan tetap digunakan agar URL artikel lama tidak berubah.




