rwslinkman / metronome
Requires
- php: >=8.1
- ext-json: *
- doctrine/doctrine-fixtures-bundle: ^3.4
- doctrine/orm: ^2.12
- mockery/mockery: ^1.5
- symfony/browser-kit: ^6.1
- symfony/dom-crawler: ^4.1|^5.0|^6.1
- symfony/finder: ^6.1
- symfony/form: ^6.1
- symfony/security-bundle: ^6.1
- symfony/yaml: ^6.1
Requires (Dev)
- pdepend/pdepend: ^2.5
- phploc/phploc: ^7.0
- phpmd/phpmd: ^2.6
- phpunit/phpunit: ^9.5
- sebastian/phpcpd: ^6.0
- squizlabs/php_codesniffer: ^3.2
README
该项目已过时,不应再使用。
请阅读 DEPRECATION NOTICE 获取更多信息。
Metronome 是为 Symfony 3 和 Symfony 4(PHP)应用提供的轻量级测试工具。
它提供了一个稳定的基座,便于进行模拟和注入 Symfony 容器。
让 Metronome 帮助您编排手头的 Symfony!
Metronome 致力于使 Symfony 项目的功能测试更加容易。
它创建一个自定义的 Kernel
和 Container
,并注入设置完整 Symfony 环境所需的一切。
您的应用将自动使用 Symfony 的 WebTestCase
和 KernelBrowser
客户端加载。
Metronome 提供了几个构建器以帮助您进行测试
MetronomeBuilder
MetronomeDoctrineMockBuilder
MetronomeFileSystemBuilder
使用 Metronome,您可以
- 构建一个
MetronomeEnvironment
,向测试Controller
类发送 GET 和 POST 请求 - 构建一个模拟的
EntityManager
以测试具有数据库交互的类 - 构建一个模拟的
ReferenceRepository
以测试您的Fixture
类 - 构建一个模拟的
ManagerRegistry
以测试您的EntityRepository
类 - 注入
MetronomeLoginData
以绕过您的GuardAuthenticator
保护 - 使用
MetronomeFormDataBuilder
和MetronomeEntityFormDataBuilder
模拟Symfony Forms
- 验证 Symfony
FlashBag
的内容
DEPRECATION NOTICE
该项目不再处于活跃开发状态。
最初,当 Symfony 没有完全支持测试的依赖注入时,该项目被创建。
此外,创建可用于 Symfony 内核的模拟也非常困难。
当时测试 Symfony 控制器相当困难。现在不再是这种情况了。
多年来,Symfony 和 PHPUnit 在这些方面都取得了巨大的进步。
因此,Metronome 正在变得越来越过时。
它对项目来说更多的是负担而不是好处。
使用改进的依赖注入系统,无需 Metronome 就可以很好地测试 Symfony 控制器。
如果您想从 Metronome 迁移,请查看 PHPUnit 中的模拟。
这些模拟可以轻松地传递给测试时的控制器构造函数。
安装
您可以使用 composer
通过 Packagist 安装 Metronome。
composer require-dev rwslinkman/metronome
由于 Metronome 是测试工具,因此在生产环境中不需要。
最新版本是 3.0.0
。
旧版本可能适用于 Symfony 3 项目,但这可能很困难,且不受支持。
对于前沿开发,请指向 dev-develop
或 dev-master
版本。
用法
Metronome 与 PHPUnit 和 Symfony 的 WebTestCase
结合使用,后者扩展了 PHPUnit 的 TestCase
。
MetronomeBuilder
使用 MetronomeBuilder
,您可以创建一个完整的应用环境。
到数据库的连接和 EntityManager
会自动模拟。
您可以注入自己的内核,或者使用现成的MetronomeKernel。
设置完成后,注入您需要的服务、存储库和其他对象。调用build
函数会返回一个MetronomeEnvironment
,它允许您向应用程序发送GET
、POST
和PUT
请求。
为您的应用程序创建一个没有模拟的环境(排除Doctrine)
$clientBuilder = new MetronomeTestClientBuilder(); $builder = new MetronomeBuilder($clientBuilder->build()); $environment = $builder->build(); $response = $environment->get("/"); $this->assertEquals(200, $response->getStatusCode());
测试控制器
使用MetronomeBuilder
设置一个MetronomeEnvironment
。
最基础的测试会从Symfony\HttpFoundation
返回一个Response
。
确保您的测试扩展了WebTestCase
,以便使用Symfony的Client
进行HTTP请求。
class IndexControllerTest extends WebTestCase { /** @var MetronomeBuilder */ private $testEnvBuilder; public function setUp() { $clientBuilder = new MetronomeTestClientBuilder(); $clientBulder->controller(IndexController::class); $this->testEnvBuilder = new MetronomeBuilder($clientBuilder->build()); $this->testEnvBuilder->setupController(IndexController::class); } public function test_givenApp_whenGetIndex_thenShouldReturnOK() { /** @var MetronomeEnvironment */ $environment = $builder->build(); /** @var Response */ $response = $environment->get("/"); $this->assertEquals(200, $response->getStatusCode()); } }
如果您在Symfony的services.yaml
中配置了自定义服务,那么您可以在测试中模拟这些服务。
当测试Controllers
时,这很方便。您可以使用MetronomeDynamicMockBuilder
轻松地模拟类。
class IndexControllerTest extends WebTestCase { /** @var MetronomeBuilder */ private $testEnvBuilder; public function setUp() { $clientBuilder = new MetronomeTestClientBuilder(); $clientBulder->controller(IndexController::class); $this->testEnvBuilder = new MetronomeBuilder($clientBuilder->build()); $this->testEnvBuilder->setupController(IndexController::class); } public function test_givenApp_whenGetIndex_andEmptyList_thenShouldReturnOK() { $mockBuilder = new MetronomeDynamicMockBuilder(UserService::class); $mockBuilder->method("getUsers", array()); $this->testEnvBuilder->injectObject("myapp.user_service", $mockBuilder->build()); /** @var MetronomeEnvironment */ $testEnv = $this->testEnvBuilder->build(); /** @var Response */ $result = $testEnv->get("/"); $this->assertEquals(200, $result->getStatusCode()); } }
对于您作为服务注入的更多静态组件,您可以使用ServiceInjector
返回基本值。
可选地,添加一些setter来设置模拟函数的期望结果。
MetronomeBuilder
有一个特殊的injectService()
函数来接受这类注入。
class ProductServiceInjector implements ServiceInjector { private $loadAllProducts; /** * @return array Key => Value array of methods to mock and their respective results */ public function inject() { return array( "loadAllProducts" => $this->loadAddProducts, ); } /** * @return string The service name as defined in config.yml */ public function serviceName() { return "rwslinkman.products"; } /** * @return string Full namespace for the service to mock */ public function serviceClass() { return '\rwslinkman\Service\ProductService'; } }
参数和定义
在测试Controllers
时,您需要处理由Symfony注入的构造函数参数。
Metronome提供了MetronomeArgument
,当Symfony需要时,它会将模拟目录注入到Controller构造函数中。
提供参数名称和您想注入的服务,然后在您的MetronomeBuilder
上调用setupController
。Metronome提供了一些预制的参数。
$clientBuilder = new MetronomeTestClientBuilder(); $clientBuilder->controller(ProjectController::class); $this->builder = new MetronomeBuilder($clientBuilder->build()); $this->builder->setupController(ProjectController::class, array( new MetronomeServiceArgument("projectsService", "my_app.projects_service"), new MetronomeFormFactoryArgument("formFactory"), new MetronomeSessionArgument("session") ));
注意:在构建MetronomeEnvironment
时必须将服务注入到容器中。
Symfony可能希望在调用控制器之前加载系统服务。
在这种情况下,向测试客户端添加一个MetronomeDefinition
。
$clientBuilder = new MetronomeTestClientBuilder();
$clientBuilder->controller(ProjectController::class);
$clientBuilder->addDefinition(new MetronomeDefinition(TokenStorage::class, TokenStorageInterface::class));
$clientBuilder->addDefinition(new MetronomeDefinition(AuthenticationUtils::class));
模拟数据库和EntityManager
使用Doctrine EntityManager
的类可以使用Metronome进行测试。
这些通常是您的Symfony应用程序中的服务。
您可以将RepoInjector
类注入以模拟您的EntityRepository
。
$metronomeBuilder = new MetronomeDoctrineMockBuilder(); $metronomeBuilder->injectRepo(new ProductRepoInjector()); $entityManagerMock = $metronomeBuilder->buildEntityManager(ProductRepository::class); $service = new ProductService($this->entityManager);
RepoInjector
与ServiceInjector
非常相似。
它围绕单个DoctrineEntity
。
class ProductRepoInjector implements RepoInjector { private $findAll; /** * @return array Key => Value array of methods to mock and their respective results */ public function inject() { return array( "findAll" => $this->findAll, ); } /** * @return mixed Acts as an identifier for the repository */ public function repositoryName() { return ProductRepository::class; } /** * @return string Full namespace for the repository to mock */ public function repositoryClass() { return '\rwslinkman\Repository\ProductRepository'; } }
测试固定数据
固定数据是Doctrine FixtureBundle
的一部分。
Metronome可以用来验证一些Fixture
的行为。
use PHPUnit\Framework\TestCase; class MyFixtureTest extends TestCase { public function test_givenFixture_whenLoad_shouldAlwaysPersist() { $envBuilder = new MetronomeDoctrineMockBuilder(); $mockEm = $envBuilder->buildEntityManager(); $fixture = new MyFixture(); $fixture->load($mockEm); $mockEm->shouldHaveReceived("flush"); } }
绕过Guard身份验证系统
在测试控制器时,您可以通过将MetronomeLoginData
注入到环境构建器中来绕过您的GuardAuthenticator
。
根据Symfony文档,认证器在您的services.yaml
中定义。
此服务标识符用于创建MetronomeLoginData
。使用此功能在测试期间绕过在security.yaml
中配置的防火墙。
使用requiresLogin
函数不是强制性的。如果您想测试您的防火墙,请省略调用。
class AdminControllerTest extends WebTestCase { /** @var MetronomeBuilder */ private $testEnvBuilder; /** @var */ MetronomeLoginData */ private $loginData; public function setUp() { $clientBuilder = new MetronomeTestClientBuilder(); $clientBulder->controller(AdminController::class); $this->testEnvBuilder = new MetronomeBuilder($clientBuilder->build()); $this->testEnvBuilder->setupController(AdminController::class); $myUser = new MyUser(); // implements UserInterface $this->loginData = new MetronomeLoginData($myUser, "rwslinkman.my_authenticator"); } public function test_givenApp_whenGetIndex_andEmptyProductList_thenShouldReturnOK() { // Add this line to actually use the login data. $this->testEnvBulder->requiresLogin($loginData); /** @var MetronomeEnvironment */ $testEnv = $this->testEnvBuilder->build(); /** @var Response */ $result = $testEnv->get("/admin"); // A route that is protected by the firewall in security.yaml $this->assertEquals(200, $result->getStatusCode()); } }
在控制器中测试表单
大多数Symfony网站在它们的控制器中都有表单,这些表单可以很容易地用Metronome进行测试。
Metronome提供了两个构建器,它们构建一个MetronomeFormData
对象。
这些数据可以注入到MetronomeBuilder
中,以模拟一个表单。
如果您将正在修改的对象的实例提供给Symfony FormBuilder,则提交有效表单时将直接更新。
您可以使用MetronomeEntityFormDataBuilder
来模拟这个更新的对象。
$entity = new MyEntity(); $entityFormBuilder = new MetronomeEntityFormDataBuilder(); $entityFormBuilder ->formData($doctrineEntity) ->isValid(true); $formData = $$entityFormBuilder->build();
如果您有更简单的表单,您可以直接使用输入数据,那么您可以使用MetronomeFormDataBuilder
。
它允许直接将值注入到表单字段中。
// Simple forms $formBuilder = new MetronomeFormDataBuilder(); $formBuilder ->isValid(true) ->formData("form_field_address", "some address") ->formData("form_field_zipcode", "123456"); $formData = $formBuilder->build();
通过将其注入到MetronomeBuilder
,可以轻松使用构建的表单数据。
$clientBuilder = new MetronomeTestClientBuilder(); $testEnvBuilder = new MetronomeBuilder($clientBuilder->build()); $envBuilder->injectForm($formData); $testEnv = $this->envBuilder->build(); $testEnv->post("/register");
您可以使用injectForm
多次。
FormFactory模拟将按注入顺序返回表单。
在某些情况下,您可能想编写针对第二个表单的特定测试。要跳过第一个表单,您可以在实际的MetronomeFormData
之前注入MetronomeNonSubmittedForm
或MetronomeInvalidForm
。
请注意,此示例使用CSS选择器,需要symfony/css-selector
依赖项。
验证FlashBag数据
用户会话中的FlashBag
可以成为您网站的便捷工具。
Metronome允许您通过MetronomeEnvironment
访问FlashBag
。
这可以帮助您更好地验证GET或POST请求的结果。
在下面的示例中,假设页面没有要显示的日志消息,并在闪存消息中报告这一点。
$clientBuilder = new MetronomeTestClientBuilder(); $testEnvBuilder = new MetronomeBuilder($clientBuilder->build()); $testEnv = $testEnvBuilder->build(); $testEnv->get("/admin/logs"); $flash = $testEnv->getFlashBag(); $this->assertNotEmpty($flash);
getFlashBag
函数返回一个关联数组,其中包含每个键的条目。
每个键条目也是一个数组,包含与该键关联的闪存消息。