支持Laravel.io的持续发展 →

如何(何时)伪造Eloquent模型

3 Feb, 2022 4 min read

在最近的一个Laravel项目中(遵循TDD原则),我遇到了这样一个问题

我应该如何伪造对不属于我的应用程序的数据库的调用?

我的应用程序需要从Oracle数据库中将数据导入到其MySQL数据库中。我不想在代码库中重新创建Oracle数据库。我该如何处理仅运行某些迁移?但是,我想确保导入控制器按照预期执行,所以我这么想

我只是简单地模拟对Oracle数据库的任何调用并返回我所期望它返回的内容。

有一个可以使用的模式,称为仓库模式:它作为应用程序和数据库之间的一个层。让我们开始吧!

设置

  • 一个模型 Order,它在MySQL中有一个对应的表
  • 一个接口 OracleOrderInterface,它定义了我们的模型需要实现哪些方法
  • 一个模型 OracleOrder,它实现了OracleOrderInterface以进行真实世界的使用
  • 一个模型 FakeOracleOrder,用于测试目的,它实现了 OracleOrderInterface
  • 一个控制器 ImportOrdersFromOracleController,它从Oracle数据库中收集订单并将其导入到MySQL数据库中

Order模型

这是一个普通的Eloquent模型。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    //
}

OracleOrderInterface

该接口定义了需要实现的方法。

<?php

namespace App\Interfaces;

interface OracleOrderInterface
{
    public static function orders();
}

OracleOrder模型

这是一个真实世界使用模型,它连接到Oracle数据库。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use App\Interfaces\OracleOrderInterface;

class OracleOrder extends Model implements OracleOrderInterface
{
    protected $connection = 'oracle';

    public static function orders()
    {
        return self::all();
    }
}

FakeOracleOrder模型

这是我们在测试中使用的一个伪造模型。它不连接到任何数据库,只是返回一个由Illuminate\Support\Collection扩展的类的集合。

<?php

namespace App;

use Illuminate\Support\Collection;
use App\Interfaces\OracleOrderInterface;

class FakeOracleOrder implements OracleOrderInterface
{
    public static function orders()
    {
        return collect([
            new class extends Collection {
            },
        ]);
    }
}

从Oracle导入订单控制器

这个控制器处理导入。在控制器中调用 __invoke() 时,我们将 OracleOrderInterface 注入其中。

<?php

namespace App\Http\Controllers;

use App\Order;
use App\Interfaces\OracleOrderInterface;

class ImportOrdersFromOracleController extends Controller
{
    public function __invoke(OracleOrderInterface $oracleOrder)
    {
        $oracleOrder::orders()
            ->each(function ($orderToImport) {
                Order::create($orderToImport->toArray());
            });
    }
}

类绑定

现在,我们需要告诉Laravel使用哪个 OracleOrderInterface 的实现,这将在 AppServiceProvider.php 中完成。

<?php

namespace App\Providers;

use App\OracleOrder;
use Illuminate\Support\ServiceProvider;
use App\Interfaces\OracleOrderInterface;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->app->bind(
            OracleOrderInterface::class, 
            OracleOrder::class
        );
    }
}

编写测试时,我们将用 FakeOracleOrder 交换 OracleOrder

测试

让我们编写一个测试,确保我们的导入控制器完成了其工作。

PHPUnit XML

Laravel标准的 phpunit.xml,扩展如下

  • <env name="DB_CONNECTION" value="sqlite"/>
  • <env name="DB_DATABASE" value=":memory:"/>
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>

        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
        <env name="MAIL_DRIVER" value="array"/>
        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
    </php>
</phpunit>

路由

我们添加一个路由来处理导入。

<?php

Route::post('/import', 'ImportOrdersFromOracleController');

测试

首先,我们将当前的 OracleOrderInterface 用我们的 FakeOracleOrder 实现替换。然后我们向 /import 发送POST请求,并断言响应正常,Oracle中的订单被导入到MySQL中。

<?php

namespace Tests\Feature;

use App\Order;
use Tests\TestCase;
use App\FakeOracleOrder;
use App\Interfaces\OracleOrderInterface;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ImportOrdersFromOracleTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function it_imports_orders_from_oracle()
    {
        $this->app->bind(
            OracleOrderInterface::class, 
            FakeOracleOrder::class
        );

        $response = $this->post('/import');

        $response->assertOk();
        $this->assertSame(1, Order::count());
    }
}

运行测试

让我们运行测试,希望成功!

$ phpunit
PHPUnit 8.3.5 by Sebastian Bergmann and contributors.

.1 / 1                                         (100%)

Time: 240 ms, Memory: 20.00 MB

OK (1 test, 2 assertions)

成功了!太棒了!

结论

我不建议为所有Eloquent模型使用模型库模式,只需(如果可能)使用 RefreshDatabase 在SQLite上运行您的测试,这将先运行您的迁移。在我的情况下,我既不想为测试创建单独的Oracle数据库,也不想模拟对 OracleOrder 的每个调用,这会类似于对您代码的拼写检查,并且不会提供任何额外的价值。

最后更新于1年前。

driesvints 喜欢了这篇文章

1
喜欢这篇文章? 让作者知道,并且给他们一个掌声!
mazedlx (mazedlx) 全栈开发者。❤️ Laravel 和 TALL Stack。父亲和烧烤爱好者。

你可能喜欢的其他文章

2024年3月11日

如何使用 Larastan 将您的 Laravel 应用从0到9

使用 Larastan 在您的 Laravel 应用执行之前发现错误是可能的,因为...

阅读文章
2024年7月19日

无需 traits 标准化 API 响应

我发现大多数用于API响应的库都是使用 traits 实现的,并且...

阅读文章
2024年7月17日

通过 Discord 通知在 Laravel 项目中收集反馈

如何在 Laravel 项目中创建一个反馈模块,当收到一条消息时如何收到 Discord 通知...

阅读文章

感谢以下这些 令人惊叹的公司 对我们的支持

您的标志在这里吗?

Laravel.io

用于解决问题、知识分享和社区建设的 Laravel 平台。

© 2024 Laravel.io - 版权所有。