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
- 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
No comments:
Post a Comment