ShoppingCart, Address); testing Eloquent models is unnecessarytests/Unit
Cart, a helper method, …
tests/Feature
composer create-project* installs PHPUnit 🤔
/phpunit.xml contains your testing environment variables
arrayDB_CONNECTION can be set to sqlite.env.testing
.env.testing overrules .env when testing (or when adding --env=testing to artisan commands)
tests/TestCase.php is the parent class of all test classes.
Leave it there!
php artisan make:test ExampleTest --unit
php artisan make:test UserTest
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function test_basic_test(): void
{
$this->assertTrue(true);
}
}
php artisan test
phpunit command, may passed here as well
php artisan test --testsuite=Feature --filter=UserTest --stop-on-failure
public function test_homepage_lists_recent_tasks(): void
{
// Arrange
$this->seed();
// Act
$response = $this->get('/');
// Assert
$response->assertOk();
$response->assertSee('Recent tasks');
}
RefreshDatabase trait does this for you:
use Illuminate\Foundation\Testing\RefreshDatabase;
class TaskTest extends TestCase
{
use RefreshDatabase;
public function test_index_lists_recent_tasks(): void
{
// ...
}
}
$this->seed(); // run all seeders
$this->seed(TaskSeeder::class); // or one specific seeder
Useful when you have a meaningful "starter dataset"
$user = User::factory()->create();
$tasks = Task::factory()->count(10)->create();
php artisan make:factory TaskFactory --model=Task
Database\Factories\<Model>Factory is auto-linked to App\Models\<Model>.HasFactory trait on the model !!!class TaskFactory extends Factory
{
public function definition(): array
{
return [
'description' => fake()->sentence(),
'priority' => fake()->randomElement(['low', 'medium', 'high']),
'completed' => false,
'user_id' => User::factory(),
];
}
}
$task = Task::factory()->create();
$tasks = Task::factory()->count(5)->create();
$mine = Task::factory()->create(['description' => 'Buy milk']);
TaskFactory above):
'user_id' => User::factory(),
public function highPriority(): static
{
return $this->state(fn (array $attrs) => ['priority' => 'high']);
}
// usage
Task::factory()->count(3)->highPriority()->create();
get, post, put, patch or
delete HTTP request, returning a Illuminate\Testing\TestResponse object
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_a_basic_request(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
$this->withHeaders([
'X-Header' => 'Value',
])->post('/user', ['name' => 'Sally']);
$user = User::factory()->create(); // see database/factories/UserFactory.php
$response = $this->actingAs($user)
->withSession(['banned' => false])
->get('/');
$response->dumpHeaders(), $response->dumpSession() and $response->dump()
$response->assertOk(); // 200
$response->assertCreated(); // 201
$response->assertUnauthorized(); // 401
$response->assertNotFound(); // 404
$response->assertStatus(422);
$response->assertRedirect(route('login'));
$response->assertSee('Create new task');
$response->assertSeeText('Welcome');
$response->assertSessionHasErrors('title'); // SSR
$response->assertJsonValidationErrors(['title']); // JSON API
getJson, postJson, …
$response = $this->postJson('/api/user', ['name' => 'Sally']);
$response
->assertStatus(201)
->assertJson([
'created' => true,
]);
// in array format:
// $this->assertTrue($response['created']);
->assertExactJson(['created' => true,])
->assertJsonPath('team.owner.name', 'Darian')
->assertJson(fn (AssertableJson $json) =>
$json->where('id', 1)
->where('name', 'Victoria Faith')
->missing('password')
->etc()
);
Read more on Fluent JSON Testing
$this->assertDatabaseHas('tasks', [
'description' => 'Buy milk',
'user_id' => $user->id,
]);
$this->assertDatabaseMissing('tasks', ['description' => 'Old task']);
$this->assertDatabaseCount('tasks', 0);
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
Storage::fake('public');
$file = UploadedFile::fake()->image('avatar.jpg', 400, 400);
$response = $this->post('/profile', ['avatar' => $file]);
// And assert it was stored on the fake disk
Storage::disk('public')->assertExists($file->hashName());
TestResponse object
https://firstnamelastname.ikdoeict.be:8443/ using your Odisee-credentials
git clone https://gitlab.com/ikdoeict/joris.maervoet/laravel-tasks-api.git
tasks.jorismaervoet.ikdoeict.be
<home>/laravel-tasks-api/app/public
composer install --no-dev)
composer.lock and vendor in the File Manager and retrycd laravel-tasks-api/app/
cp .env.example .env
php artisan key:generate
.env
APP_ENV=production
APP_DEBUG=false
APP_URL=https://tasks.jorismaervoet.ikdoeict.be
DB_HOST=localhost
DB_DATABASE=(copy from your notepad)
DB_USERNAME=(copy from your notepad)
DB_PASSWORD=(copy from your notepad)
In case it's a web API used from an SPA from other subdomain with auth:
SANCTUM_STATEFUL_DOMAINS=ikdoeict.be,frontend.bartdelrue.ikdoeict.be
SESSION_DOMAIN=.ikdoeict.be
php artisan storage:link
php artisan migrate --force
php artisan db:seed --force
⚠ Warning: only run db:seed on production if your seeders are idempotent and safe (no fake/random data, no destructive truncates). Otherwise skip it.
php artisan config:cache
php artisan event:cache
php artisan route:cache
php artisan view:cache
php artisan optimizephp artisan optimize:clearphp artisan down
php artisan down
git pull
composer install --no-dev
php artisan migrate --force
php artisan optimize
php artisan up
composer require laravel/envoy --dev
/Envoy.blade.php:
@servers(['web' => ['user@192.168.1.1'], 'workers' => ['user@192.168.1.2']])
@task('deploy', ['on' => 'web'])
cd /home/user/example.com
git pull origin {{ $branch }}
php artisan migrate --force
@endtask
php vendor/bin/envoy run deploy --branch=master
ssh-keygen -t rsa -b 2048 (or reuse the key pair in ~/.ssh)/Envoy.blade.php. Envoy will use the SSH key from the underlying OS.
lorisleiva/laravel-docker:8.3 container to build, lint, test and deploy
ssh-keygen -t rsa -b 2048phpunit.xml, enable testing with in-memory SQLite:
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
RefreshDatabase …
<?php
namespace Tests\Feature;
use App\Models\Task;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class TaskControllerTest extends TestCase
{
use RefreshDatabase;
protected User $standardUser;
protected User $adminUser;
protected function setUp(): void
{
parent::setUp();
$this->standardUser = User::factory()->create(['role' => 'standard']);
$this->adminUser = User::factory()->create(['role' => 'admin']);
}
public function test_a_standard_user_can_only_see_his_own_tasks()
{
$task = Task::factory()->create(['user_id' => $this->standardUser->id]);
Task::factory()->create(); // Task for someone else
$response = $this->actingAs($this->standardUser)->getJson('/api/tasks');
$response->assertOk();
$response->assertJsonCount(1, 'data');
$response->assertJsonFragment(['id' => $task->id]);
}
public function test_an_admin_can_see_all_tasks()
{
Task::factory()->count(3)->create();
$response = $this->actingAs($this->adminUser)->getJson('/api/tasks');
$response->assertOk();
$response->assertJsonCount(3, 'data');
}
}
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class TaskFactory extends Factory
{
public function definition(): array
{
return [
'description' => fake()->sentence,
'priority' => fake()->randomElement(['low', 'medium', 'high']),
'user_id' => User::factory(),
'created_at' => now(),
];
}
}
.gitlab-ci.yml:
image: lorisleiva/laravel-docker:8.3
stages:
- build
- lint
- test
- deploy
default:
before_script:
- cd app
# Build Stage
composer:
stage: build
script:
- composer install --no-interaction --no-ansi --no-progress
- cp .env.example .env
- php artisan key:generate
artifacts:
paths:
- app/vendor/
- app/.env
expire_in: 1h
# Lint stage
pint:
stage: lint
needs:
- job: composer
artifacts: true
script:
- vendor/bin/pint --test
# Test Stage
unit_test:
stage: test
needs:
- job: composer
artifacts: true
script:
- php artisan test
# Deploy Stage
deploy:
stage: deploy
script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- chmod 400 "$SSH_PRIVATE_KEY"
- ssh-add "$SSH_PRIVATE_KEY"
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo -e "Host *\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
- ssh $SSH_USERNAME@cloudplesk.ikdoeict.be "export PATH=/opt/plesk/php/8.3/bin:\$PATH && cd /data/vhosts/jorismaervoet.ikdoeict.be/laravel-tasks-api-11 && git pull origin master && cd app && composer install --no-interaction --prefer-dist --optimize-autoloader && php artisan migrate --force && php artisan optimize"
only:
- master