leandromatos's avatar

How to test if relationship has successfully created?

In a test case, how to test if relationship has successfully created?

Example:

<?php

use App\School;
use App\Teacher;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class TeacherTest extends TestCase
{
    use DatabaseTransactions;

    /** @test */
    public function a_teacher_can_be_created()
    {
        // Given
        // create a teacher
        $teacher = Teacher::create([
            'name'     => 'Albert Einstein',
            'email'    => '[email protected]',
            'password' => bcrypt(123),
        ]);

        // add school to teacher
        $schools = factory(School::class, 2)->create();
        $teacher->addSchool($schools);

        // Wheen
        // get a latest teacher
        $latestTeacher = Teacher::latest()->first();

        // Then
        // assert equals: teacher is equal latest teacher
        $this->assertEquals($teacher->id, $latestTeacher->id);
        $this->assertEquals('Albert Einstein', $latestTeacher->name);
        $this->assertEquals('[email protected]', $latestTeacher->email);

        // ?????????? assert $teacher belongs to $school ??????????
        // 

        // see a teacher in database
        $this->seeInDatabase('teachers', [
            'name'  => 'Albert Einstein',
            'email' => '[email protected]',
        ]);
    }
}
0 likes
7 replies
jlrdw's avatar

Well the old-fashioned way would work simply do a query and ensure you're getting proper results that you expect.

leandromatos's avatar

I did something like this:

public function scopeHasSchool($query, $school)
    {
        if ($school instanceof School) {
            $method = 'where';
            $school = $school->id;
        } else {
            $method = 'whereIn';
            $school = $school->pluck('id')->all();
        }

        return !!$query->whereHas('schools', function ($query) use ($method, $school) {
            $query->$method('id', $school);
        })->count();
    }

... and then I tested so:

$this->assertTrue($teacher->hasSchool($schools));

Would have a better way to do?

leandromatos's avatar
leandromatos
OP
Best Answer
Level 34

I solved like this:

TeacherTest.php

<?php 

use App\School;
use App\Teacher;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class TeacherTest extends TestCase
{
    use DatabaseTransactions;
    use SoftDeletes;
    use MailTracking;

    /** @test */
    public function a_teacher_can_be_created()
    {
        // Given
        // create a teacher
        $teacher = Teacher::create([
            'name'     => 'Albert Einstein',
            'email'    => '[email protected]',
            'password' => bcrypt(123),
        ]);

        // add school to teacher
        // $school = factory(School::class)->create();
        // $teacher->addSchool($school);
        $schools = factory(School::class, 2)->create();
        $teacher->addSchool($schools);

        // send a welcome e-mail
        Mail::raw('Helo World', function ($message) {
            $message->to('[email protected]');
            $message->from('[email protected]');
            $message->subject('Teacher created!');
        });

        // When
        // get a latest teacher
        $latestTeacher = Teacher::latest()->first();

        // Then
        // assert equals: teacher is equal latest teacher
        $this->assertEquals($teacher->id, $latestTeacher->id);
        $this->assertEquals('Albert Einstein', $latestTeacher->name);
        $this->assertEquals('[email protected]', $latestTeacher->email);

        // $this->assertTrue($teacher->hasSchool($school));
        $this->assertTrue($teacher->hasSchool($schools));

        // see a email sent to teacher
        $this->seeEmailEquals('Helo World')
            ->seeEmailSubjectEquals('Teacher created!');

        // see a teacher in database
        $this->seeInDatabase('teachers', [
            'name'  => 'Albert Einstein',
            'email' => '[email protected]',
        ]);
    }

    /** @test */
    public function a_teacher_can_be_deleted()
    {
        // Given
        // create a teacher
        factory(Teacher::class)->create();

        // When
        // get a latest teacher created
        $latestTeacher = Teacher::latest()->first();

        // Then
        // delete a latest teacher created and ...
        $latestTeacher->delete();

        // test teacher trashed
        $this->assertTrue($latestTeacher->trashed());
    }
}
Teacher.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Teacher extends Model
{
    use SoftDeletes;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    public function schools()
    {
        return $this->belongsToMany(School::class);
    }

    /**
     * Add school to a teacher
     *
     * @param App\School|Illuminate\Database\Eloquent\Collection $school
     * @return Array
     */
    public function addSchool($school)
    {
        if ($school instanceof School) {
            return $this->schools()->sync([$school->id]);
        }

        return $this->schools()->sync($school->pluck('id')->all());
    }

    public function hasSchool($school)
    {
        if ($school instanceof School) {
            return (boolean) $this->schools()
                ->where('id', $school->id)
                ->count();
        }

        return (boolean) $this->schools()
            ->whereIn('id', $school->pluck('id'))
            ->count();
    }
}
1 like
impbob36's avatar

I do something similar ....

public function testArticleHasCommentRelationship()
{
        // SETUP:       Create new Article
        $article= factory(App\Article::class)->create();

        // SETUP:       Create new comment
        $comment = factory(App\Comment::class)->create();

        // ASSERT:      Comment does not belong to Article
        $this->notSeeInDatabase('comments', [
            'id'          => $comment->id,
            'article_id' => $article->id,
        ]);


        // Update Article to have Comment
        $article->comments()->save($comment);


        // ASSERT:      Comment does belongs to Article
        $this->seeInDatabase('comments', [
            'id'          => $comment->id,
            'article_id' => $article->id,
        ]);

        // ASSERT:      Eloquent relationship returns model
        $this->assertInstanceOf('App\Comment', $article->comments()->first());
}
1 like
miigaa's avatar

I would do like this:

/** @test */
public function a_teacher_can_teach_for_multiple_schools()
{
    $teacher = factory(Teacher::class)->create();
    $school1 = factory(School::class)->create();
    $school2 = factory(School::class)->create();

    $school1->addTeacher($teacher);
    $school2->addTeacher($teacher);

    $this->assertCount(2, $teacher->schools);
    $this->assertTrue($teacher->teachesFor($school1));
    $this->assertTrue($teacher->teachesFor($school2));
}

And teachesFor methods would be

public function teachesFor(School $school)
{
    if ($this->relationLoaded('schools')) {
        return $this->schools->contains($school);
    }

    return $this->schools()
        ->wherePivot('school_id', $school->id)
        ->exists();
}

And i think you no need to test the delete operation that using SoftDeletes. That already tested by the core developers.

1 like

Please or to participate in this conversation.