LaravelでモデルのUnitテストを書く時の注意
はじめに
以下の記事にあるように、インスタンス化したモデルに自分で値をセットすることで、Unitテストを行うことができます。
ですが、この時プロパティをdate
へキャストする設定を行うとエラーが発生します。その時の解決法です。
TL;DR
- モデルのプロパティを
Datetime
へキャストするときは
<?php
...
class User extends Authenticatable
{
...
protected $casts = [
'active_from' => 'datetime' // active_fromカラムの値をDatetimeへキャストする
];
public function isActive(): bool
{
return $this->active_from < Carbon::now();
}
}
-
setDateFormat()
を呼ぶ
<?php
namespace Tests\Unit;
use App\Models\User;
use Carbon\Carbon;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
public function test_isAdmin()
{
$user = new User();
$user->setDateFormat('Y-m-d H:i:s'); // これ
$user->active_from = Carbon::yesterday();
self::assertTrue($user->isActive());
}
}
キャストの設定
Datetime
へキャストするときは<?php
...
class User extends Authenticatable
{
...
protected $casts = [
'active_from' => 'datetime' // active_fromカラムの値をDatetimeへキャストする
];
public function isActive(): bool
{
return $this->active_from < Carbon::now();
}
}
setDateFormat()
を呼ぶ<?php
namespace Tests\Unit;
use App\Models\User;
use Carbon\Carbon;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
public function test_isAdmin()
{
$user = new User();
$user->setDateFormat('Y-m-d H:i:s'); // これ
$user->active_from = Carbon::yesterday();
self::assertTrue($user->isActive());
}
}
次のようなモデルを想定します。$casts
を使って、active_from
をDatetime
へキャストする設定を書いています。
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
protected $fillable = [
'name',
'password',
'active_from'
];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'active_from' => 'datetime' // active_fromカラムの値をDatetimeへキャストする
];
public function isActive(): bool
{
return $this->active_from < Carbon::now();
}
}
Unitテストを書いてみる
Unitテストは以下のようになります。一見動きそうですが、実行するとエラーになります。
<?php
namespace Tests\Unit;
use App\Models\User;
use Carbon\Carbon;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
public function test_isActive()
{
$user = new User();
$user->active_from = Carbon::yesterday();
self::assertTrue($user->isActive());
}
}
PHPUnit 9.5.5 by Sebastian Bergmann and contributors.
Error : Call to a member function connection() on null
/xxxxx/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:1569
/xxxxx/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:1535
/xxxxx/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php:1139
/xxxxx/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php:1087
/xxxxx/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php:740
/xxxxx/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:1904
/xxxxx/tests/Unit/UserTest.php:16
Time: 00:00.027, Memory: 12.00 MB
ERRORS!
Tests: 1, Assertions: 0, Errors: 1.
何故なのか
スタックトレースを追ってみます。
まず、HasAttributes.php:740
を見ます。(とんでもない行数だ...)
このsetAttribute()
関数はUserモデルのactive_from
に値をセットする時に呼ばれる関数のようです。
public function setAttribute($key, $value)
{
// First we will check for the presence of a mutator for the set operation
// which simply lets the developers tweak the attribute as it is set on
// this model, such as "json_encoding" a listing of data for storage.
if ($this->hasSetMutator($key)) {
return $this->setMutatedAttributeValue($key, $value);
}
// If an attribute is listed as a "date", we'll convert it from a DateTime
// instance into a form proper for storage on the database tables using
// the connection grammar's date format. We will auto set the values.
elseif ($value && $this->isDateAttribute($key)) {
$value = $this->fromDateTime($value); // ここ!
}
if ($this->isClassCastable($key)) {
$this->setClassCastableAttribute($key, $value);
return $this;
}
if (! is_null($value) && $this->isJsonCastable($key)) {
$value = $this->castAttributeAsJson($key, $value);
}
// If this attribute contains a JSON ->, we'll set the proper value in the
// attribute's underlying array. This takes care of properly nesting an
// attribute in the array's value in the case of deeply nested items.
if (Str::contains($key, '->')) {
return $this->fillJsonAttribute($key, $value);
}
if (! is_null($value) && $this->isEncryptedCastable($key)) {
$value = $this->castAttributeAsEncryptedString($key, $value);
}
$this->attributes[$key] = $value;
return $this;
}
続いてHasAttributes.php:740
から呼ばれるHasAttributes.php:1087
を見ます。fromDateTime()
関数はモデルにセットされた値をDB用へ変換する関数のようです。続いてfromDateTime()
から呼ばれるgetDateFormat()
を見ます。
/**
* Convert a DateTime to a storable string.
*
* @param mixed $value
* @return string|null
*/
public function fromDateTime($value)
{
return empty($value) ? $value : $this->asDateTime($value)->format(
$this->getDateFormat()
);
}
DBにDatetime型の値を保存するときの変換フォーマットを取得するメソッドです。ここで、$this->dateFormat
が取得できなかったときに$this->getConnection()
としてDBからフォーマットを取得しようとします。上の記事にあるように、UnitテストではDBアクセスを行うことができないので、エラーが発生してしまいます。
/**
* Get the format for database stored dates.
*
* @return string
*/
public function getDateFormat()
{
return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat();
}
どうするのか
答えは単純で、$this->dateFormat
にフォーマットを自分でセットしてしまえばOKです。そのためのsetDateFormat()
というメソッドがありますのでこちらを使います。
<?php
namespace Tests\Unit;
use App\Models\User;
use Carbon\Carbon;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
public function test_isAdmin()
{
$user = new User();
$user->setDateFormat('Y-m-d H:i:s'); // これ
$user->active_from = Carbon::yesterday();
self::assertTrue($user->isActive());
}
}
テストが通る!
PHPUnit 9.5.5 by Sebastian Bergmann and contributors.
Time: 00:00.054, Memory: 10.00 MB
OK (1 test, 1 assertion)
Author And Source
この問題について(LaravelでモデルのUnitテストを書く時の注意), 我々は、より多くの情報をここで見つけました https://qiita.com/ggg-mzkr/items/830e0db764e801c1269f著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .