Laravel 4 模型事件不适用于 PHPUnit

Posted

技术标签:

【中文标题】Laravel 4 模型事件不适用于 PHPUnit【英文标题】:Laravel 4 Model Events don't work with PHPUnit 【发布时间】:2013-06-29 23:11:03 【问题描述】:

我在 Laravel 4 中使用 creating 模型事件构建模型端验证:

class User extends Eloquent 

    public function isValid()
    
        return Validator::make($this->toArray(), array('name' => 'required'))->passes();
    

    public static function boot()
    
        parent::boot();

        static::creating(function($user)
        
            echo "Hello";
            if (!$user->isValid()) return false;
        );
    

它运行良好,但我遇到了 phpUnit 问题。以下两个测试完全相同,但只是第一个通过:

class UserTest extends TestCase 

    public function testSaveUserWithoutName()
    
        $count = User::all()->count();

        $user = new User;
        $saving = $user->save();

        assertFalse($saving);                       // pass
        assertEquals($count, User::all()->count()); // pass
    

    public function testSaveUserWithoutNameBis()
    
        $count = User::all()->count();

        $user = new User;
        $saving = $user->save();

        assertFalse($saving);                       // fail
        assertEquals($count, User::all()->count()); // fail, the user is created
    

如果我尝试在同一个测试中创建一个用户两次,它会起作用,但就像绑定事件仅出现在我的测试类的第一个测试中一样。在第一次测试执行期间,echo "Hello"; 仅打印一次。

我简化了我的问题的案例,但您可以看到问题:我无法在不同的单元测试中测试多个验证规则。从几个小时开始,我几乎尝试了所有事情,但我现在快要跳出窗户了!有什么想法吗?

【问题讨论】:

阅读github.com/laravel/framework/issues/1181 谢谢。最后,模型事件不容易测试。我用这个技巧解决了我的问题:我在我的setUp() 方法中调用User::boot() 我更喜欢使用User::observe(new UserObserver),这样你就可以自己测试UserObserver了。 @AlexandreButynski 我也做了同样的事情,最后它是针对这个问题的简洁且包罗万象的解决方案,但似乎没有适当的修复。希望这是可行的,这要归功于 Eloquent 模型上的 __callStatic 方法(Model::bootprotected),它为我们省去了在模型上定义公共方法以使其可测试的麻烦。感谢您的提示! 【参考方案1】:

这个问题在 Github 中有详细的记录。请参阅上面的 cmets 进一步解释。

我已经修改了 Github 中的一个“解决方案”,以在测试期间自动重置所有模型事件。将以下内容添加到您的 TestCase.php 文件中。

app/tests/TestCase.php

public function setUp()

    parent::setUp();
    $this->resetEvents();



private function resetEvents()

    // Get all models in the Model directory
    $pathToModels = '/app/models';   // <- Change this to your model directory
    $files = File::files($pathToModels);

    // Remove the directory name and the .php from the filename
    $files = str_replace($pathToModels.'/', '', $files);
    $files = str_replace('.php', '', $files);

    // Remove "BaseModel" as we dont want to boot that moodel
    if(($key = array_search('BaseModel', $files)) !== false) 
        unset($files[$key]);
    

    // Reset each model event listeners.
    foreach ($files as $model) 

        // Flush any existing listeners.
        call_user_func(array($model, 'flushEventListeners'));

        // Reregister them.
        call_user_func(array($model, 'boot'));
    

【讨论】:

它以比我的基本技巧更通用的方式解决了我的问题(只需为每个模型添加一行 User::boot()),但它让我感到困惑有两个原因:它添加了一堆奇怪的代码在我的 TestCase 类中,它会在每次测试之前解析文件(这可能会导致性能问题)... 需要测试模型是否先扩展模型,但有时它们可​​能没有flushEventListeners方法。【参考方案2】:

我的模型在子目录中,所以我稍微编辑了@TheShiftExchange 代码

//Get all models in the Model directory
$pathToModels = '/path/to/app/models';
$files = File::allFiles($pathToModels);

foreach ($files as $file) 
    $fileName = $file->getFileName();
    if (!ends_with($fileName, 'Search.php') && !starts_with($fileName, 'Base')) 
        $model = str_replace('.php', '', $fileName);
        // Flush any existing listeners.
        call_user_func(array($model, 'flushEventListeners'));
        // Re-register them.
        call_user_func(array($model, 'boot'));
    

【讨论】:

以上是关于Laravel 4 模型事件不适用于 PHPUnit的主要内容,如果未能解决你的问题,请参考以下文章

Laravel4:块函数不适用于模型保存()

phpunit 命令不适用于 Windows 7 上的 laravel 4

RESTful Paypal API WebHooks Simulator 不适用于 Laravel

Laravel attach()方法不适用于hasMany方面

Laravel 分页不适用于数组而不是集合

yajra/laravel-datatables 搜索不适用于 laravel 5.4