Skip to main content
The project includes comprehensive testing and code quality tools for both backend and frontend code.

Testing Stack

PHPUnit

Backend testing framework for unit and feature tests

Laravel Pint

PHP code style fixer based on PHP-CS-Fixer

ESLint

JavaScript/TypeScript linter with Vue support

Prettier

Code formatter for consistent styling

Backend Testing

The project uses PHPUnit for Laravel testing:

Configuration

phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" colors="true">
    <testsuites>
        <testsuite name="Unit">
            <directory>tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory>tests/Feature</directory>
        </testsuite>
    </testsuites>
    <source>
        <include>
            <directory>app</directory>
        </include>
    </source>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
        <env name="CACHE_STORE" value="array"/>
        <env name="QUEUE_CONNECTION" value="sync"/>
        <env name="SESSION_DRIVER" value="array"/>
    </php>
</phpunit>

Test Structure

tests/
├── Feature/               # Feature/integration tests
│   ├── Auth/
│   │   ├── AuthenticationTest.php
│   │   ├── RegistrationTest.php
│   │   ├── PasswordResetTest.php
│   │   └── TwoFactorChallengeTest.php
│   ├── DashboardTest.php
│   ├── Settings/
│   │   └── PasswordUpdateTest.php
│   └── ExampleTest.php
└── Unit/                  # Unit tests
    └── ExampleTest.php

Example Feature Test

tests/Feature/DashboardTest.php
namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class DashboardTest extends TestCase
{
    use RefreshDatabase;

    public function test_guests_are_redirected_to_the_login_page()
    {
        $response = $this->get(route('dashboard'));
        $response->assertRedirect(route('login'));
    }

    public function test_authenticated_users_can_visit_the_dashboard()
    {
        $user = User::factory()->create();
        $this->actingAs($user);

        $response = $this->get(route('dashboard'));
        $response->assertOk();
    }

    public function test_dashboard_displays_correct_stats()
    {
        $user = User::factory()->create();
        $this->actingAs($user);

        // Create test data
        Tenant::factory()->count(5)->create(['status' => 'Active']);
        Tenant::factory()->count(3)->create(['status' => 'Trial']);

        $response = $this->get(route('dashboard'));

        $response->assertOk();
        $response->assertInertia(fn ($page) => $page
            ->component('system/Dashboard')
            ->has('stats', fn ($stats) => $stats
                ->where('total_tenants', 8)
                ->where('active_tenants', 5)
                ->where('trial_tenants', 3)
            )
        );
    }
}

Testing Multi-Tenancy

tests/Feature/TenantTest.php
public function test_tenant_can_be_created()
{
    $user = User::factory()->create();
    $this->actingAs($user);

    $plan = Plan::factory()->create();

    $response = $this->post(route('tenants.store'), [
        'name' => 'Test Company',
        'owner_name' => 'John Doe',
        'owner_email' => '[email protected]',
        'owner_password' => 'password123',
        'plan_id' => $plan->id,
        'domain' => 'testcompany',
    ]);

    $response->assertRedirect(route('tenants.index'));
    $this->assertDatabaseHas('tenants', [
        'name' => 'Test Company',
        'owner_email' => '[email protected]',
    ]);
}

Running PHP Tests

composer test

Frontend Testing

While the project doesn’t include frontend tests by default, you can add them using Vitest or Cypress.
1

Install Dependencies

pnpm add -D vitest @vue/test-utils happy-dom
2

Configure Vitest

vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
    test: {
        environment: 'happy-dom',
        globals: true,
    },
    // ... other config
});
3

Write Tests

tests/components/AppHeader.test.ts
import { mount } from '@vue/test-utils';
import { describe, it, expect } from 'vitest';
import AppHeader from '@/components/AppHeader.vue';

describe('AppHeader', () => {
    it('renders title prop', () => {
        const wrapper = mount(AppHeader, {
            props: { title: 'Dashboard' },
        });

        expect(wrapper.text()).toContain('Dashboard');
    });
});
4

Add Script

package.json
{
    "scripts": {
        "test": "vitest",
        "test:ui": "vitest --ui"
    }
}

Code Quality

PHP Linting with Pint

Laravel Pint automatically fixes code style issues:
pint.json
{
    "preset": "laravel"
}
composer lint
# or
./vendor/bin/pint

JavaScript Linting with ESLint

ESLint with TypeScript and Vue support:
eslint.config.js
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';
import prettier from 'eslint-config-prettier';
import importPlugin from 'eslint-plugin-import';
import vue from 'eslint-plugin-vue';

export default defineConfigWithVueTs(
    vue.configs['flat/essential'],
    vueTsConfigs.recommended,
    {
        ignores: [
            'vendor',
            'node_modules',
            'public',
            'bootstrap/ssr',
            'resources/js/components/ui/*',
        ],
    },
    {
        plugins: {
            import: importPlugin,
        },
        rules: {
            'vue/multi-word-component-names': 'off',
            '@typescript-eslint/no-explicit-any': 'off',
            '@typescript-eslint/consistent-type-imports': [
                'error',
                {
                    prefer: 'type-imports',
                    fixStyle: 'separate-type-imports',
                },
            ],
            'import/order': [
                'error',
                {
                    groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
                    alphabetize: {
                        order: 'asc',
                        caseInsensitive: true,
                    },
                },
            ],
        },
    },
    prettier,
);
pnpm run lint

Code Formatting with Prettier

.prettierrc
{
    "semi": true,
    "singleQuote": true,
    "tabWidth": 4,
    "trailingComma": "all",
    "printWidth": 100,
    "plugins": ["prettier-plugin-tailwindcss"]
}
pnpm run format

Continuous Integration

Example GitHub Actions workflow:
.github/workflows/tests.yml
name: Tests

on: [push, pull_request]

jobs:
  tests:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.2
          extensions: mbstring, xml, sqlite3

      - name: Install Composer Dependencies
        run: composer install --prefer-dist --no-progress

      - name: Run PHP Tests
        run: composer test

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install pnpm
        run: npm install -g pnpm

      - name: Install NPM Dependencies
        run: pnpm install

      - name: Run ESLint
        run: pnpm run lint

      - name: Check Formatting
        run: pnpm run format:check

      - name: Build Assets
        run: pnpm run build

Test Database

The test suite uses SQLite in-memory database for speed:
phpunit.xml
<php>
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="DB_DATABASE" value=":memory:"/>
</php>

Refresh Database

Use the RefreshDatabase trait to reset the database between tests:
use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    public function test_example()
    {
        // Database is fresh for each test
    }
}

Running All Quality Checks

Run all tests and linters before committing:
# Backend
composer test      # Runs PHPUnit tests and Pint linting

# Frontend
pnpm run lint      # ESLint
pnpm run format    # Prettier

# Build
pnpm run build     # Verify build succeeds

Pre-commit Hooks

Consider using Husky to run tests automatically:
1

Install Husky

pnpm add -D husky lint-staged
npx husky init
2

Configure Pre-commit

.husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# Run PHP linting
composer test:lint

# Run frontend linting
pnpm run lint
pnpm run format:check
3

Configure Lint-Staged

package.json
{
    "lint-staged": {
        "*.php": ["./vendor/bin/pint"],
        "*.{js,ts,vue}": ["eslint --fix", "prettier --write"]
    }
}

Testing Best Practices

// Good
public function test_authenticated_users_can_create_tenants()

// Bad
public function test_create()
// Good
$user = User::factory()->create();
$tenants = Tenant::factory()->count(5)->create();

// Bad
$user = new User(['name' => 'Test', 'email' => '[email protected]']);
$user->save();
Each test should verify one behavior or scenario.
public function test_example()
{
    // Arrange
    $user = User::factory()->create();
    $this->actingAs($user);

    // Act
    $response = $this->post('/tenants', $data);

    // Assert
    $response->assertRedirect();
    $this->assertDatabaseHas('tenants', ['name' => 'Test']);
}
Use RefreshDatabase to ensure tests don’t affect each other.

Code Coverage

Generate code coverage reports:
php artisan test --coverage --min=80
This ensures at least 80% of your code is covered by tests.

Performance Testing

For performance testing, consider:
  • Laravel Telescope: Monitor queries and performance
  • Laravel Debugbar: View queries, execution time, memory usage
  • Blackfire: Profile PHP applications
composer require --dev barryvdh/laravel-debugbar

Next Steps

Project Structure

Understand the codebase organization

Deployment

Deploy your application to production

Build docs developers (and LLMs) love