Q:

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

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? 

A:

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 $hasApprovedBeenInvoked = false;

    public function isApproved()
    {
        $this->hasApprovedBeenInvoked = 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->hasApprovedBeenInvoked);
    }
}

However, this will only work if Student::create() uses late static binding and returns an instance of StutentStub.  
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

https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers
https://laravel.com/docs/5.4/eloquent#events
http://php.net/manual/en/language.oop5.late-static-bindings.php

