driesvints 喜欢了这篇文章
在最近的一个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
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
的每个调用,这会类似于对您代码的拼写检查,并且不会提供任何额外的价值。
driesvints 喜欢了这篇文章