Belajar Yii - Testing (Codeception)
Series/Belajar Yii/Episode 16
Episode 16 of 27

Belajar Yii - Testing (Codeception)

Mengamankan kualitas aplikasi dengan Codeception: menyiapkan framework testing untuk Yii, menulis unit test untuk logika model, functional test untuk alur request-response, acceptance test untuk skenario browser end-to-end, serta memakai fixtures untuk data test yang terisolasi dan deterministik.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Sejauh ini kita membangun fitur dengan cepat. Tapi kecepatan tanpa jaring pengaman adalah utang: satu perubahan kecil di model Post bisa merusak halaman index, dan kalian baru tahu setelah production error. Testing adalah jaring pengaman itu — dan di ekosistem Yii, tool standarnya adalah Codeception.

Codeception menawarkan empat lapis pengujian yang saling melengkapi: unit (logika murni), functional (alur request internal), acceptance (lewat browser sungguhan), dan fixtures (data test deterministik). Di episode ini kita menyiapkan semuanya, menulis test untuk CRUD dan API yang kita bangun di episode 5 dan 10, lalu menjalankannya sebagai bagian rutin pengembangan.

Codeception: Pengenalan dan Setup

Codeception adalah framework testing PHP dengan konsep suites — setiap suite adalah lingkungan test dengan modul pendukung berbeda. Template Yii2 basic sudah menyertakan Codeception; jika belum, install:

Install Codeception dan codeception module Yii2
composer require codeception/codeception --dev
composer require yiisoft/yii2-codeception --dev

Template basic sudah menyediakan struktur tests/:

text
tests/
├── _support/
│   ├── FunctionalTester.php
│   ├── UnitTester.php
│   └── AcceptanceTester.php
├── unit/          # unit test (model, helper)
├── functional/    # functional test (request-response)
├── acceptance/    # acceptance test (browser)
├── fixtures/      # data test
├── codeception.yml
└── unit.suite.yml, functional.suite.yml, acceptance.suite.yml

codeception.yml mengonfigurasi keseluruhan, termasuk koneksi database test terpisah:

tests/codeception.yml
actor: Tester
paths:
    tests: tests
    output: tests/_output
    support: tests/_support
    data: tests
settings:
    bootstrap: _bootstrap.php
    colors: true
    memory_limit: 1024M
params:
    - env.php
modules:
    config:
        Yii2:
            configFile: '../config/test.php'
            cleanup: true

Kunci penting: configFile: '../config/test.php' — file konfigurasi yang memakai database test terpisah (misal SQLite atau DB khusus test), agar test tidak menyentuh data pengembangan.

Unit Test: Menguji Logika Model

Unit test memverifikasi logika murni — validasi, relasi, method model — tanpa HTTP. Contoh menguji Post:

tests/unit/models/PostTest.php
<?php
 
namespace tests\unit\models;
 
use app\models\Post;
use Codeception\Test\Unit;
use tests\fixtures\PostFixture;
 
class PostTest extends Unit
{
    public function _fixtures(): array
    {
        return [
            'posts' => PostFixture::class,
        ];
    }
 
    public function testRulesRequireTitle(): void
    {
        $post = new Post();
        $post->body = 'Konten';
        $post->status = 'draft';
 
        $this->assertFalse($post->validate());
        $this->assertArrayHasKey('title', $post->getErrors());
    }
 
    public function testSlugMustBeUnique(): void
    {
        $post = new Post([
            'title' => 'Post duplikat',
            'slug' => 'post-pertama',  // sudah dipakai fixture
            'status' => 'draft',
        ]);
 
        $this->assertFalse($post->validate());
        $this->assertArrayHasKey('slug', $post->getErrors());
    }
 
    public function testValidPostPasses(): void
    {
        $post = new Post([
            'title' => 'Post valid',
            'slug' => 'post-valid',
            'body' => 'Konten lengkap',
            'status' => 'published',
        ]);
 
        $this->assertTrue($post->validate());
    }
}
Jalankan unit test
vendor/bin/codecept run unit

Hasil: testRulesRequireTitle memverifikasi title wajib, testSlugMustBeUnique memverifikasi validasi unique terhadap fixture, dan testValidPostPasses memastikan data benar lolos. Test ini deterministik — jalan kapan pun, hasil sama.

Functional Test: Alur Request-Response

Functional test menjalankan aplikasi Yii di dalam memori — request dibuat, controller dipanggil, response diperiksa, tanpa browser sungguhan:

tests/functional/post/PostCreateCest.php
<?php
 
namespace tests\functional;
 
use app\models\User;
use FunctionalTester;
 
class PostCreateCest
{
    public function testCreateRequiresLogin(FunctionalTester $I): void
    {
        $I->amOnPage('/post/create');
        $I->see('Login', 'h1');
    }
 
    public function testCreatePostAsLoggedInUser(FunctionalTester $I): void
    {
        $I->amLoggedInAs(User::findByUsername('admin'));
        $I->amOnPage('/post/create');
        $I->fillField('#post-title', 'Post dari functional test');
        $I->fillField('#post-body', 'Konten test');
        $I->click('Simpan');
        $I->see('Post disimpan.');
        $I->seeRecord('app\models\Post', ['title' => 'Post dari functional test']);
    }
}
Jalankan functional test
vendor/bin/codecept run functional

Yang diuji bukan sekadar "halaman bisa dibuka", melainkan perilaku: user guest ditolak akses create, user login bisa membuat post, dan post benar-benar tersimpan di database (seeRecord). Inilah tes yang paling banyak memberikan nilai per jam penulisannya — ringan, cepat, dan mencakup alur MVC lengkap.

Acceptance Test: Skenario Browser Nyata

Acceptance test menggunakan browser sungguhan (via WebDriver) — menguji hal yang tidak bisa ditangkap functional test: JavaScript, CSS, klik nyata:

tests/acceptance/PostCrudCest.php
<?php
 
namespace tests\acceptance;
 
use AcceptanceTester;
 
class PostCrudCest
{
    public function testCrudFlow(AcceptanceTester $I): void
    {
        $I->amOnPage('/post');
        $I->see('Post Blog', 'h1');
 
        $I->click('Buat Post');
        $I->fillField('#post-title', 'Post dari acceptance test');
        $I->fillField('#post-body', 'Konten acceptance');
        $I->click('Simpan');
 
        $I->see('Post disimpan.');
        $I->see('Post dari acceptance test');
    }
}
Jalankan acceptance test (butuh WebDriver)
vendor/bin/codecept run acceptance

Acceptance test butuh WebDriver (ChromeDriver/Selenium) berjalan. Karena lebih berat, jadikan acceptance test sebagai lapisan terakhir yang menangkap skenario paling kritis — bukan untuk setiap perubahan kecil.

Tip

Strategi proporsi yang sehat: banyak unit test (murah, cepat, mencakup logika), functional test untuk setiap alur penting (login, CRUD, API), dan acceptance test hanya untuk skenario end-to-end kritis. Banyak tim membuat kesalahan dengan menulis acceptance test untuk segalanya, lalu test suite jadi lambat dan jarang dijalankan.

Fixtures: Data Test yang Terisolasi

Fixtures adalah data yang disediakan untuk test — deterministik dan bersih setiap kali test dijalankan. Contoh fixture untuk Post:

tests/fixtures/PostFixture.php
<?php
 
namespace tests\fixtures;
 
use yii\test\ActiveFixture;
 
class PostFixture extends ActiveFixture
{
    public $modelClass = 'app\models\Post';
    public $depends = [
        'tests\fixtures\UserFixture',
    ];
}
tests/fixtures/data/post.php
<?php
 
return [
    'post1' => [
        'title' => 'Post Pertama',
        'slug' => 'post-pertama',
        'body' => 'Konten fixture pertama',
        'status' => 'published',
        'user_id' => 1,
        'created_at' => 1710000000,
        'updated_at' => 1710000000,
    ],
    'post2' => [
        'title' => 'Post Kedua',
        'slug' => 'post-kedua',
        'body' => 'Konten fixture kedua',
        'status' => 'draft',
        'user_id' => 1,
        'created_at' => 1710003600,
        'updated_at' => 1710003600,
    ],
];

Setiap test yang mendeklarasikan _fixtures() mendapat data bersih yang di-reload otomatis sebelum test berjalan. Perhatikan dependsPostFixture membutuhkan UserFixture terlebih dahulu agar foreign key valid. Fixtures juga bisa dipakai di luar test, misal untuk seeding data pengembangan (episode 6).

Menguji REST API

API yang kita bangun di episode 10 juga bisa ditest secara functional — baik lewat module Yii2 maupun HTTP API:

tests/functional/api/PostApiCest.php
<?php
 
namespace tests\functional\api;
 
use app\models\User;
use FunctionalTester;
 
class PostApiCest
{
    public function testIndexRequiresToken(FunctionalTester $I): void
    {
        $I->sendGET('/api/posts');
        $I->seeResponseCodeIs(401);
    }
 
    public function testIndexWithToken(FunctionalTester $I): void
    {
        $user = User::findByUsername('admin');
        $token = $user->access_token;
 
        $I->amHttpAuthenticated('', $token);
        $I->sendGET('/api/posts');
        $I->seeResponseCodeIs(200);
        $I->seeResponseIsJson();
        $I->seeResponseContainsJson(['title' => 'Post Pertama']);
    }
}
Jalankan semua test
vendor/bin/codecept run

Dua hal yang dijamin: API menolak tanpa token (401), dan dengan token benar mengembalikan data JSON yang sesuai — termasuk perilaku yang sering kena regresi saat serializer atau autentikasi berubah.

Mengintegrasikan Test ke Workflow

Test yang tidak dijalankan otomatis akan terlupakan. Paling tidak, jalankan test di CI pipeline setiap push — kita detailkan di episode 22. Untuk kebiasaan lokal, buat script di composer.json:

composer.json - script test
{
    "scripts": {
        "test": "vendor/bin/codecept run",
        "test:unit": "vendor/bin/codecept run unit"
    }
}
Jalankan test via composer
composer test

Dengan satu perintah, seluruh suite berjalan — dan perintah yang sama dipakai CI.

Penutup

Inti yang harus dibawa pulang:

  • Codeception memiliki 4 lapis: unit (logika), functional (request internal), acceptance (browser), fixtures (data deterministik).
  • configFile: config/test.php mengisolasi test ke database test terpisah.
  • Fixtures dengan ActiveFixture + depends menyediakan data bersih per test.
  • Functional test mencakup CRUD dan API: status code, JSON, dan state database.
  • Proporsi sehat: banyak unit, functional untuk alur penting, acceptance untuk skenario kritis.
  • Otomatisasi via composer test dan CI.

Di episode 17 selanjutnya, kita mengeraskan aplikasi: security best practices — menangkal XSS dengan Html::encode, CSRF token, SQL injection lewat parameter binding, password hashing yang benar, serta security headers untuk production. Inilah episode yang menentukan apakah aplikasi kalian bertahan di internet sungguhan. Sampai jumpa di episode 17!

Belajar Yii - Testing (Codeception) | Belajar Yii