First let’s create the database using phpMyAdmin
database: simple_api_rest
collation: utf8mb4_unicode_ci
Create a laravel project using one of this alternatives.
composer create-project laravel/laravel simple_api_rest
composer global require laravel/installer laravel new simple_api_rest
Don’t select a starter kit.
We will use PHPUnit for testing.
Select mysql as a database.
Now run the command
php artisan install:api
This command create a api.php file in routes folter.
Each route defined in that file have the /api prefix by default.
Remove the route that was generated.
Creating the model Post with their migration.
php artisan make:model Post -m
This command will create the migration in database/migration
And the model in app/Models
public function up(): void { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('content'); $table->timestamps(); }); }
Creating the PostFactory.
php artisan make:factory PostFactory
This command will create the factory in database/factories
public function definition(): array { return [ "title" => $this->faker->title(), "content" => $this->faker->paragraph(), ]; }
Creating the PostSeeder
php artisan make:seeder PostSeeder
This command will create the seeder in database/seeders
use App\Models\Post; class PostSeeder extends Seeder { /** * Run the database seeds. */ public function run(): void { Post::factory(10)->create(); } }
Open the file seeders/DatabaseSeeder
public function run(): void { $this->call([ PostSeeder::class, ]); }
Run the migration and the seeder
php artisan migrate:fresh # fresh will remove all the data saved php artisan db:seed # run the seeders or php artisan migrate:fresh --seed
You can see data created in the posts table in phpMyAdmin
Creating a test to view if data was created
php artisan make:test PostTest
This command will create the test in tests/Feature
use PHPUnit\Framework\Attributes\Test; class PostTest extends TestCase { #[Test] public function posts_must_be_created(): void { $this->assertDatabaseCount('posts', 10); } }
Run the tests
php artisan test # run all the tests or php artisan test --filter posts_must_be_created # run all test that matches
Using a database in memory to run the tests
Open phpunit.xml and uncomment two lines
<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="PULSE_ENABLED" value="false"/> <env name="QUEUE_CONNECTION" value="sync"/> <env name="SESSION_DRIVER" value="array"/> <env name="TELESCOPE_ENABLED" value="false"/> </php>
If we run the tests again will throw an error because the seeder wasn’t executed.
class PostTest extends TestCase { use RefreshDatabase; // Create the database and run the migrations in each test protected function setUp(): void { parent::setUp(); $this->seed(PostSeeder::class); // Run the seeder PostSeeder } #[Test] public function posts_must_be_created(): void { $this->assertDatabaseCount('posts', 10); } }
Creating controller
php artisan make:controller PostController --api --model=Post
This command will create the controller in app/Http/Controllers
use App\Models\Post; use Illuminate\Http\Request; class PostController extends Controller { public function index() { $posts = Post::all(); return response()->json($posts); } public function store(Request $request) { $data = $request->validate([ 'title' => 'required|string|max:255', 'content' => 'required|string', ]); $post = Post::create($data); return response()->json($post, 201); } public function show(Post $post) { return response()->json($post); } public function update(Request $request, Post $post) { $data = $request->validate([ 'title' => 'sometimes|required|string|max:255', 'content' => 'sometimes|required|string', ]); $post->update($data); return response()->json($post); } public function destroy(Post $post) { $post->delete(); return response()->json(null, 204); } }
Defining routes
use App\Http\Controllers\PostController; use Illuminate\Support\Facades\Route; Route::apiResource('posts', PostController::class);
Command to see the routes defined
php artisan route:list or php artisan r:l
CRUD tests
class PostTest extends TestCase { use RefreshDatabase; // Create the database and run the migrations in each test protected function setUp(): void { parent::setUp(); $this->seed(PostSeeder::class); // Run the seeder PostSeeder } #[Test] public function posts_must_be_created(): void { $this->assertDatabaseCount('posts', 10); } #[Test] public function must_show_post_list(): void { $response = $this->getJson(route('posts.index')); $response->assertStatus(200); $response->assertJsonCount(10); $response->assertJsonStructure([ '*' => [ 'id', 'title', 'content', 'created_at', 'updated_at', ] ]); } #[Test] public function must_show_post(): void { $response = $this->getJson(route('posts.show', 1)); $response->assertStatus(200); $response->assertJsonStructure([ 'id', 'title', 'content', 'created_at', 'updated_at', ]); } #[Test] public function must_create_post(): void { $response = $this->postJson(route('posts.store'), [ 'title' => 'Test Post', 'content' => 'This is a test post', ]); $response->assertStatus(201); $response->assertJsonStructure([ 'id', 'title', 'content', 'created_at', 'updated_at', ]); $this->assertDatabaseHas('posts', [ 'title' => 'Test Post', 'content' => 'This is a test post', ]); } #[Test] public function must_update_post(): void { $response = $this->putJson(route('posts.update', 1), [ 'title' => 'Updated Test Post', 'content' => 'This is an updated test post', ]); $response->assertStatus(200); $response->assertJsonStructure([ 'id', 'title', 'content', 'created_at', 'updated_at', ]); $this->assertDatabaseHas('posts', [ 'id' => 1, 'title' => 'Updated Test Post', 'content' => 'This is an updated test post', ]); } #[Test] public function must_delete_post(): void { $response = $this->deleteJson(route('posts.destroy', 1)); $response->assertStatus(204); $this->assertDatabaseMissing('posts', [ 'id' => 1, ]); } }
Running tests
Update and create tests will throw an error
We’re using mass assignment but still not defined the attributes that can received.
Open Post in app/Models
class Post extends Model { use HasFactory; protected $fillable = [ 'title', 'content', ]; }
Runing the tests again
php artisan test or php artisan test --filter PostTest
Top comments (0)