如何用 phpunit 模拟 Laravel Eloquent 模型?

Posted

技术标签:

【中文标题】如何用 phpunit 模拟 Laravel Eloquent 模型?【英文标题】:How to mock Laravel Eloquent model with phpunit? 【发布时间】:2021-12-23 07:59:16 【问题描述】:

我有一个基于 Illuminate\Database\Eloquent\Model 的 Laravel User 模型。

它在我要进行单元测试的服务中使用。这就是为什么我想模拟具有某些属性的User,在这种情况下,它是关于应该设置为23id

我通过以下方式创建我的模拟:

/**
 * @param int $id
 * @return User|\phpUnit\Framework\MockObject\MockObject
 */
protected function createMockUser(int $id): \PHPUnit\Framework\MockObject\MockObject|User

    $user = $this
        ->getMockBuilder(User::class)
        ->disableOriginalConstructor()
        ->getMock();
    $user->id = $id;

    return $user;

我也试过了:

$user->setAttribute('id', $id);

但在这两种情况下,模拟上的 id 属性将是 null

如何在 Laravel 模型的 mock 上设置属性?

【问题讨论】:

我解决了我的问题,方法是不将模型注入我的服务,而是直接注入它的 id,但对于某些服务,这种方法仍然会崩溃(假设我想访问它上面的多个值)。在这种情况下,就足够了。 可以创建一个空的User::class模型并设置id:new User(['id' => 23]);或者使用模型工厂:laravel.com/docs/master/… Mocking Laravel 模型不能解决任何问题,它可以使用工厂创建,而不是使用数据库,该功能可以使用 sqlite 数据库或类似数据库。如果您只需要访问属性,即使没有数据库访问权限的空模型也可以工作。 【参考方案1】:

正如我在评论中所说,您不应该模拟用户。 Laravel 有多种工具可以解决这个问题,主要原因是您必须模拟不必要的长调用链,静态函数和部分模拟不适用于表命名和保存操作。 Laravel 有很好的测试文档,例如。 testing with a database,它回答了Laravel 中的大多数测试最佳实践。


这里有两种方法,您可以创建工厂来创建永远不会保存在数据库中的用户。对工厂调用 create() 会将其保存到数据库中,但调用 make() 会用数据填充模型而不是保存它。

class UserFactory extends Factory

    public function definition()
    
        return [
            'name' => $this->faker->name(),
            'email' => $this->faker->unique()->safeEmail(),
        ];
    


// id is normally set by database, set it to something.
$user = User::factory()->make(['id' => 42]);

取而代之的是单元测试和更多面向功能的测试。为了性能和测试环境中的易用性,默认使用 sqlite 会更容易。

添加以下内容。

config/database.php

'sqlite' => [
    'driver' => 'sqlite',
    'url' => env('DATABASE_URL'),
    'database' => ':memory:',
    'prefix' => '',
    'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],

phpunit.xml

<server name="DB_CONNECTION" value="sqlite"/>

在你的测试中使用这个特性。

use RefreshDatabase;

现在您应该可以使用以下内容了。这将提供一个完整的用户模型,其中包含您需要的所有功能。 sqlite 的唯一缺点是您使用的版本,不支持外键和奇怪的数据库特定功能。

public function test_your_service()

    $result = resolve(YourService::class)->saveUserData(User::factory()->create());

    // assert

【讨论】:

感谢您的回答,我必须写一些类似的东西,但您节省了我的时间。我将添加一些指向文档的链接,任何用户在提出任何问题之前都应该阅读这些链接,因为 Laravel 拥有有史以来最好的文档之一......所有内容都在那里进行了解释,而且大部分时间都很清楚...现在,对于作者:文档有自己的 testing 部分,所以如果您阅读它来补充您可以在此处找到的任何内容作为答案,那就太棒了... 我不需要像保存这样的数据库操作。基本上,我在符合类名的同时使用模拟作为 pdo。我不需要 SQLite 数据库,因为被测服务仅从模型实例中读取数据。 然后使用第一个解决方案,那里不需要数据库。但是这样关系就行不通了。我见过很多人试图模拟他们的方式模型,这既费时又不实用,所有测试的 laravel 开发人员都做了类似的事情。然后你可以争论你需要什么以及你在其他地方做什么。但这是 laravel 中的标准,一般来说,它对大多数问题都有一种非常务实的方法,并且 taylor 表示他总是想要一个不使用模拟的测试,所以他知道实现是有效的

以上是关于如何用 phpunit 模拟 Laravel Eloquent 模型?的主要内容,如果未能解决你的问题,请参考以下文章

PhpUnit - 用关系模拟 laravel 模型

Laravel / PHPUnit:运行测试时没有模拟类

Laravel PHPUnit 模拟请求

使用 Laravel 和 PHPUnit 模拟函数

PHPUnit 问题断言模拟是使用 Laravel 的 ReflectionProperty 对象

如何在 PHPUnit / Laravel 中模拟 JSON 文件