我上一篇文章关于测试API和服务的测试留下了悬念
在测试中切换实现方式还有另一种方法:管理器。但这将是另一篇博客文章的内容。
哦,这不是很烦人吗?在此提前道歉。不过,不用担心,在这篇文章中,我将揭示有关Laravel中的管理器的一切,包括它们的作用以及如何让Laravel开发者的生活变得更简单。
什么是管理器?
如果您参考我之前的文章关于测试API和服务的测试,您会注意到我们必须在TestCase
中放置以下代码。
<?php
namespace Tests;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Artisan;
use Tests\Doubles\FakeTwitterClient;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
use LazilyRefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->swap(Twitter::class, new FakeTwitterClient());
}
}
这虽然是可行的,但对于初学者来说可能不太明显,因为它的实现并不遵循Laravel的常规约定。那么,在这种情况下,Laravel的标准约定是什么?看看config/cache.php
文件。您会看到这样一些内容。
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache connection that gets used while
| using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function.
|
*/
'default' => env('CACHE_DRIVER', 'file'),
这在Laravel中很常见;我们可以在配置文件中指定应用于特定服务的实现或驱动程序。但在测试中使用假实现进行替换在哪里?检查您的项目根目录中的phpunit.xml
或phpunit.dist.xml
文件。在那里,您可能会看到类似以下内容。
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
</coverage>
<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>
这里发生了什么?当我们运行测试套件时,Pest 或 PHPUnit 会加载这个 XML 文件。它会读取这些 <server/>
节点,并使用 .env
文件中的值替换这些值。因此,在这种情况下,我们的缓存驱动器在测试中始终是 array
,即使我们在 .env
文件中定义了 redis
。多么优雅的方法!
但是,“file
”、“redis
”或“array
”这几个词是如何变为我们合约的实际实现的呢?答案是 Manager
。让我们为我们的 Twitter 服务创建一个。
创建一个 Manager
我喜欢在实现所在的目录下放置我的 Manager
类。所以,让我们在 app/Services/Twitter/TwitterManager.php
创建一个新文件。
<?php
namespace App\Services\Twitter;
use Illuminate\Support\Manager;
final class TwitterManager extends Manager
{
public function getDefaultDriver(): string
{
}
}
请注意,我们的类扩展了 Illuminate\Support\Manager
。该类有一个抽象方法,我们需要实现它:getDefaultDriver
。这个方法的目的是由应用程序使用我们的 Twitter
合约的实现来决定。注意它返回一个字符串。可能是像 file
、redis
或 array
这样的字符串。你们觉得我们会怎么进行下去呢?
让我们更新 config/services.php
中的 twitter
配置,以添加对我们的所需实现的支持。
<?php
return [
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
],
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'twitter' => [
'default' => env('TWITTER_DRIVER', 'oauth'),
'consumer_key' => env('TWITTER_CONSUMER_KEY'),
'consumer_secret' => env('TWITTER_CONSUMER_SECRET'),
'access_token' => env('TWITTER_ACCESS_TOKEN'),
'access_token_secret' => env('TWITTER_ACCESS_TOKEN_SECRET'),
],
];
现在,回到我们的 TwitterManager
中,我们可以访问并返回这个配置值。
<?php
namespace App\Services\Twitter;
use Illuminate\Support\Manager;
final class TwitterManager extends Manager
{
public function getDefaultDriver(): string
{
return $this->config->get('services.twitter.default') ?? 'null';
}
}
请注意,manager 给我们提供了一个 config
属性,这是非常有用的。同时,注意我们使用 ??
来捕获 null
值,并返回字符串 null
。这很重要。
因此,我们为我们的实现定义了两个可能的字符串值:oauth
和 null
。我们如何将这些选项翻译成相应的类?Laravel 的 Manager
类将查找在您的 manager 上定义的一个遵循以下签名的方法:createXDriver
,其中 X
是我们想要实现字符串值。让我们利用这个知识来构建我们的类。
<?php
namespace App\Services\Twitter;
use Illuminate\Support\Manager;
use Tests\Doubles\FakeTwitterClient;
use Abraham\TwitterOAuth\TwitterOAuth;
final class TwitterManager extends Manager
{
public function getDefaultDriver(): string
{
return $this->config->get('services.twitter.default') ?? 'null';
}
public function createOauthDriver(): OauthClient
{
return new OauthClient();
}
public function createNullDriver(): FakeTwitterClient
{
return new FakeTwitterClient();
}
}
很简单!如果您的实现需要服务容器中的项目,Manager 给您提供了一个 container
属性,您可以用来加载这些依赖项并将它们传递给实现。
现在,我们的 manager 不会自动被调用。我们需要更新在 AppServiceProvider
中创建的绑定,以使用此 manager。
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Contracts\Services\Twitter;
use Illuminate\Contracts\Container\Container;
use App\Services\Twitter\TwitterManager;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(Twitter::class, function (Container $container) {
return (new TwitterManager($container))->driver();
});
}
}
所以,现在让我们分解我们的应用程序在请求服务容器中的 Twitter
合约实现时将经历的流程。
- Laravel 将创建一个
TwitterManager
实例,并在其上调用driver
方法。 driver
方法(这是基础Manager
类的一部分)将调用我们先前在TwitterManager
中必须实现的getDefaultDriver
方法。getDefaultDriver
方法将查看我们的config/services.php
文件中的twitter.default
键。- 该键将被用来在 manager 上调用一个方法:
create[Key]Driver
。 create[Key]Driver
方法将返回所请求的驱动程序的实现。
明白了吗?完成这些操作后,我们现在可以更新 phpunit.xml
文件,用我们的模拟驱动程序覆盖 .env
文件中的内容。
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
</coverage>
<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
<server name="TWITTER_DRIVER" value="null"/>
</php>
</phpunit>
当然,你现在可以移除我们在 TestCase
中添加的手动交换。
结论
所以,这就是 Laravel 中的 managers!整合起来非常简单,并且功能强大。如果你将来添加了第三个实现,只需在我们 manager 中添加一个名为 createThirdImplementation
的新方法,然后在几秒钟内就可以上线。
就像往常一样,我希望你学到了一些新东西。
诚挚问候,Luke
driesvints, akhmatovalexander 喜欢这篇文章