Summer Sale! All accounts are 50% off this week.

RileyGWeb's avatar

Creating a CRUD model with TDD

I just finished the "PHP Testing Jargon" course, and I'm creating a simple to-do list practice app to get TDD under my belt.

The app structure will look like this: the user can create projects, the project can have lists, and the lists have items. This involves SQL foreign key constraints creating relationships between the lists and projects that their children belong too.

So obviously you must first create a project before you can create anything else. I want to create the functionality that creates a new project in the database proper TDD style. The problem is I have no idea what I'm doing. Here is my Project model and my newProjectTest:

Project.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;

class Project extends Model
{
    use HasFactory;

    public static function create($name)
    {
        $newProject = new Project; 
        $newProject->name = $name;
        // $newProject->user_id = Auth::id();
        $newProject->user_id = 123; // Don't know how to fake a user ID in a test
        $newProject->save();
    }
}

newProjectTest.php

<?php

namespace Tests\Unit;

use App\Models\Project;
use Tests\TestCase;

class newProjectTest extends TestCase
{
    /**
     * @return void
     */
    public function test_it_creates_a_new_project ()
    {
        $newProject = Project::create('Project 1');
        $this->assertNotNull($newProject);
    }
}

And the migration if it helps:

..._create_projects_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('projects', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            $table->string('name');
            $table->boolean('completed')->default(false);
            $table->timestamps();

            // One-to-many on projects table
            $table->foreign('user_id')
            ->references('id')
            ->on('users')
            ->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('projects');
    }
};

When I run the test, I get : Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (to_do_ninja.projects, CONSTRAINT projects_user_id_foreign FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE)

I'm super confused at this point. I'm sure there are 1,000 things I'm doing wrong, I apologize in advance lol. I'm trying to learn. While this is all new to me, I'm hoping this is something trivial for an experienced TDD developer. Any help is very much appreciated.

0 likes
3 replies
MohamedTammam's avatar
Level 51

Firstly, I recommend watching the following small list: https://www.youtube.com/playlist?list=PLk9WlAgeZoTdAsVsOHz5OijXv5jh8TZ0b

To get a model's foreign ID, you need to create it first.

public static function create($name)
{
        $newProject = new Project; 
        $newProject->name = $name;
        // $newProject->user_id = Auth::id();
		$user = User::factory()->create();
        $newProject->user_id = $user->id; // Don't know how to fake a user ID in a test
        $newProject->save();
}

However, if you want to use authenticated user ID in the tests, you need to do the following.

 public function test_it_creates_a_new_project ()
{
		$user = $user = User::factory()->create(); // Create a new user using factory class (Laravel come with a default factory)
		$this->actingAs($user); // Login as that user
        $newProject = Project::create('Project 1');
        $this->assertNotNull($newProject);
}

Now that method should work.

public static function create($name)
{
        $newProject = new Project; 
        $newProject->name = $name;
        $newProject->user_id = Auth::id();
        $newProject->save();
}
1 like
RileyGWeb's avatar

@MohamedTammam This is excellent, actingAs is really helpful.

One more question though, what is the proper assertion to use for this test? Or is it pretty standard to just check the database to see if it works, even under TDD?

MohamedTammam's avatar

@Coaster132 For me, I wouldn't test that case. I would rather send a request to the endpoint and see whether the record has been added or not.

1 like

Please or to participate in this conversation.