Latest

Tuesday, July 11, 2017

How can I test whether Eloquent model method is called by "creating" event in Laravel 5.4?

Asked by: user1878906


I would like to test whether a method is called when a eloquent event is triggered.

In the example below I would like to set the approved property automatically through isApproved() method when the Student instance is saved into the database.

Here is the Student model class:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Student extends Model
{

    public $guarded = [];


    protected static function boot()
    {
        parent::boot();

        // set approved attribute if its value is NULL
        self::creating(function (self $student) {
            $student->approved = $student->approved ?? $student->isApproved();
        });
    }

    /**
     * @return bool
     */
    public function isApproved()
    {
        return ($this->age >= 14) && ($this->age <= 20);
    }
}

To achieve this i attached a callback function on the creating event for the Student class.

I am trying to test isApproved() method invocation with the following test:

<?php

namespace Tests\Feature;

use App\Student;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class StudentTest extends TestCase
{

    use DatabaseMigrations;

    /**
     * @test
     */
    public function student_is_auto_approved_on_save()
    {

        $mock = \Mockery::mock(Student::class);
        $mock->shouldReceive('isApproved')->once();

        Student::create([
            'name' => 'John Doe',
            'age' => 14
        ]);
    }
}

but the test does not pass and the following dump is shown

    PHPUnit 5.7.21 by Sebastian Bergmann and contributors.

E                                                                   1 / 1 (100%)

Time: 246 ms, Memory: 16.00MB

There was 1 error:

1) Tests\Feature\StudentTest::student_is_auto_approved_on_save
Mockery\Exception\InvalidCountException: Method isApproved() from Mockery_0_App_Student should be called
 exactly 1 times but called 0 times.

/students/vendor/mockery/mockery/library/Mockery/CountValidator/Exact.php:37
/students/vendor/mockery/mockery/library/Mockery/Expectation.php:298
/students/vendor/mockery/mockery/library/Mockery/ExpectationDirector.php:120
/students/vendor/mockery/mockery/library/Mockery/Container.php:297
/students/vendor/mockery/mockery/library/Mockery/Container.php:282
/students/vendor/mockery/mockery/library/Mockery.php:152
/students/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:144
/home/user/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php:186
/home/user/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php:116

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

What I am doing wrong?


Answers

Answered by: localheinz at 2017-07-11 07:02PM



Try

<?php

namespace Tests\Feature;

use App\Student;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class StudentTest extends TestCase
{
    use DatabaseMigrations;

    /**
     * @test
     * @dataProvider providerAutoApprovedAge
     * 
     * @param int $age
     */
    public function student_is_auto_approved_on_save($age)
    {
        $student = Student::create([
            'name' => 'John Doe' . $age,
            'age' => $age,
        ]);

        $student->save();

        $this->assertTrue($student->approved);
    }

    public function providerAutoApprovedAge()
    {
        for ($age = 14; $age <= 20; $age++) {
            yield [
                $age,
            ];
        }
    }

    /**
     * @test
     * @dataProvider providerNotAutoApprovedAge
     * 
     * @param int $age
     */
    public function student_is_not_auto_approved_on_save($age)
    {
        $student = Student::create([
            'name' => 'John Doe' . $age,
            'age' => $age,
        ]);

        $student->save();

        $this->assertFalse($student->approved);
    }

    public function providerNotAutoApprovedAge()
    {
        for ($age = 0; $age < 14; $age++) {
            yield [
                $age,
            ];
        }

        for ($age = 21; $age < 120; $age++) {
            yield [
                $age,
            ];
        }
    }
}

You probably want to assert that the student is

  • automatically approved for ages within the required age range
  • not automatically approved for ages outside of the required age range

You could use data providers for that.

Alternatively, you could stub out the method as such:

class StudentStub extends Student
{
    public $invoked = false;

    public function isApproved()
    {
        $this->invoked = true;
    }
}

class StudentTest extends TestCase { use DatabaseMigrations;

    /**
     * @test
     */
    public function student_is_auto_approved_on_save($age)
    {
        $student = StudentStub::create([
            'name' => 'John Doe' . $age,
            'age' => $age,
        ]);

        $student->save();

        $this->assertTrue($student->invoked);
    }
}

However, this will only work if Student::create() uses late static binding.

And mostly, you don't want to mock the system under test, but collaborators, if you have any. Here, there aren't any.

For reference, see




Source

No comments:

Post a Comment

Adbox