noj / fabrica
用于创建实体工厂的库,用于测试
Requires
- php: ^7.0|^8.0
- noj/dot: ^1.0.1
Requires (Dev)
- doctrine/orm: ^2.5
- phpunit/phpunit: ^6.5|^7.5|^8.0|^9.0
Suggests
- fzaninotto/faker: Use faker to auto-generate fake entity data
README
Fabrica 处理用于测试的对象的定义和创建。
结合其 Doctrine 集成,Fabrica 允许您在不进行模拟的情况下全面测试 CRUD 逻辑。请参阅 解决的问题 页面以获取完整解释。
安装
composer require noj/fabrica --dev
使用
设置
首先在测试套件中的某个位置初始化 Fabrica。对于 PHPUnit,可以使用 引导 选项完成此操作
<?php require 'vendor/autoload.php'; Fabrica::loadFactories([__DIR__ . '/factories']);
基本用法
假设您有一个 User
实体,创建一个新的工厂 factories/UserFactory.php
Fabrica::define(User::class, function () { return [ 'username' => 'user123', 'firstName' => 'Test', 'lastName' => 'User', ]; });
现在在您的测试中,您可以创建一个新的 User
实例
$user = Fabrica::create(User::class); // Check properties have been set assertEquals('user123', $user->username); assertEquals('Test', $user->firstName); assertEquals('User', $user->lastName);
设置器
如果实体的属性只能通过设置器访问,则可以使用 @
语法来调用方法
Fabrica::define(User::class, function () { return [ '@setUsername' => 'user123', '@setFirstName' => 'Test', '@setLastName' => 'User', ]; });
给定一个值是数组,您可以使用多次后缀(*
)来指示您希望对每个项目调用该方法
'@addPermission*' => ['USER', 'ADMIN'],
覆盖
在创建实体时,您可以覆盖任何默认值
/// UserFactory.php Fabrica::define(User::class, function () { return [ 'username' => 'user123', 'firstName' => 'Test', 'lastName' => 'User', '@setAge' => 47, ]; }); /// UserTest.php $user = Fabrica::create(User::class, function () { return [ 'firstName' => 'Another', '@setAge' => 24, ]; }); assertEquals('user1223', $user->username); assertEquals('Another', $user->firstName); assertEquals('User', $user->lastName); assertEquals(24, $user->getAge());
创建多个
您可以这样创建多个实体
$users = Fabrica::createMany(User::class, 3);
关系
您可以自动创建相关实体。例如,如果您有一个属于 User
的 Comment
实体,则可以定义一个工厂
Fabrica::define(Comment::class, function () { return [ 'title' => 'A test comment', 'body' => 'This is a test', 'author' => Fabrica::create(User::class), ]; });
每次创建 Comment
时,它都将有一个相关的 User
$comment = Fabrica::create(Comment::class); assertInstanceOf(User::class, $comment->user); assertEquals('user123', $comment->user->username);
您还可以定义关系的反向。例如,您可以定义每个创建的 User
应该有一个相关的 Comment
Fabrica::define(User::class, function () { return [ '@setUsername' => 'user123', '@setFirstName' => 'Test', '@setLastName' => 'User', '@addComment' => Fabrica::create(Comment::class) ]; });
您可以创建多个子关系
Fabrica::define(User::class, function () { return [ 'comments' => Fabrica::createMany(Comment::class, 3), // or if you have a setter method, use the `*` suffix to call the method // once for each element of the array '@addComment*' => Fabrica::createMany(Comment::class, 3), ]; });
将创建一个具有 3 个 Comments
的 User
如果实体有一个依赖于关系的属性,则可以这样定义
Fabrica::define(Comment::class, function () { return [ 'user' => Fabrica::create(User::class), 'userFirstName' => Fabrica::property('user.firstName'), ]; });
覆盖关系属性
您还可以在创建实体时覆盖嵌套关系的属性
$comment = Fabrica::create(Comment::class, function () { return [ 'author.firstName' => 'John' ]; }); assertEquals('user123', $comment->user->username); assertEquals('John', $comment->user->firstName);
对于一对一关系中的一个实体
$user = Fabrica::create(User::class, function () { return [ 'comments.1.title' => 'Only the 2nd comment has this title' ]; }); assertEquals('A test comment', $user->comments[0]->title); assertEquals('Only the 2nd comment has this title', $user->comments[1]->title);
甚至每个实体
$user = Fabrica::create(User::class, function () { return [ 'comments.*.title' => 'Each comment now has this title' ]; }); foreach ($user->comments as $comment) { assertEquals('Each comment now has this title', $comment->title); }
实体类型
而不是在创建实体时始终传递覆盖,您可以定义不同类型的实体
Fabrica::define(User::class, function () { return [ 'username' => 'bannedUser', 'firstName' => 'Test', 'lastName' => 'User', ]; })->type('banned'); $normalUser = Fabrica::create(User::class); $bannedUser = Fabrica::create(User::class, 'banned'); $bannedUser2 = Fabrica::create(User::class, 'banned', function () { return ['firstName' => 'banned']; });
扩展
如果子类型与父类型共享属性,则可以指定工厂从它扩展
Fabrica::define(User::class, function () { return [ 'username' => 'bannedUser' ]; })->type('banned')->extends(User::class); $bannedUser = Fabrica::create(User::class, 'banned'); assertEquals('bannedUser', $bannedUser->username); assertEquals('Test', $bannedUser->firstName); assertEquals('User', $bannedUser->lastName);
您还可以从子类型扩展
Fabrica::define(User::class, function () { return [ 'permanent' => true ]; })->type('permaBanned')->extends(User::class, 'banned);
Doctrine 集成
Fabrica 随附一个 Doctrine 适配器,它将在创建时自动持久化您的实体。只需在引导文件中配置存储即可
Fabrica::setStore(new DoctrineStore($entityManager));
EntityManager 的来源以及其配置可能取决于您的应用程序。
Fabrica 提供了一种简单的方法,使用内存中的 SQLite 创建基于注解的 EntityManager,如果这符合您的需求
$entityManager = \Noj\Fabrica\Adapter\Doctrine\EntityManagerFactory::createSQLiteInMemory([__DIR__ . '/path/to/entities']); Fabrica::setStore(new DoctrineStore($entityManager));
在测试之间刷新数据库
很可能是您希望在每次测试运行之前重置数据库的状态。有两种方法可以做到这一点
- 如果您正在使用 PHPUnit 7.5 或更高版本,则可以在
phpunit.xml
中添加以下内容,这将在每个测试之间重置您的数据库<extensions> <extension class="Noj\Fabrica\Adapter\Doctrine\PHPUnit\RefreshDatabase" /> </extensions>
- 如果您正在使用 PHPUnit 的较低版本或只想为特定测试创建数据库,则可以将特质添加到您的测试类中
class MyTest extends TestCase { use \Noj\Fabrica\Adapter\Doctrine\PHPUnit\NeedsDatabase; }
PHPUnit 断言
Fabrica 默认提供了一组 PHPUnit 断言,用于在测试过程中验证数据库的状态。
class MyTest extends TestCase { use \Noj\Fabrica\Adapter\Doctrine\PHPUnit\DatabaseAssertions; }
这提供了以下断言:
assertDatabaseContainsEntity(string $class, array $criteria = [])
assertDatabaseContainsEntities(string $class, int $amount, array $criteria = [])
assertDatabaseContainsExactlyOneEntity(string $class, array $criteria = [])
assertDatabaseDoesNotContainEntity(string $class, array $criteria = [])
注意:如果您正在使用上述描述的 NeedsDatabase
,则断言已包含在内。
示例用法
public function test_it_creates_a_user() { (new UserCreator)->create('test'); self::assertDatabaseContainsEntity(User::class, ['username' => 'test'])' }
Faker 集成
Fabrica 可以配置一个 faker 实例,以帮助在实体定义中生成假数据。
$faker = \Faker\Factory::create(); Fabrica::addDefineArgument($faker);
然后您将在定义回调中收到 faker 实例
use Faker\Generator as Faker; Fabrica::define(User::class, function (Faker $faker) { return [ 'firstName' => $faker->firstName, 'lastName' => $faker->lastName, 'email' => $faker->email, ]; });