cydrickn / laravel-query-factory
Query Factory 用于轻松模拟查询构建器,而无需从真实数据库发送查询
Requires
- php: >=7.1.0
- ext-mbstring: *
- ext-pdo: *
- laravel/framework: ~5.5|~6.0|~7.0
Requires (Dev)
- ext-pdo_mysql: *
- laravel/laravel: ~5.5|~6.0|~7.0
- mockery/mockery: ^1.0
- php-coveralls/php-coveralls: ^2.2
- phpunit/phpunit: ~7.0|~8.0|~9.0
This package is auto-updated.
Last update: 2024-09-05 01:38:54 UTC
README
Laravel Query Factory 是一个库,让您轻松模拟查询构建器,而无需从真实数据库发送查询。
此库适用于真正遵循自动化测试的开发者,单元测试是测试整个系统的最底层。
此库旨在解决的问题
在 Laravel 中,问题之一是,大多数教程甚至 Laravel 的文档中,测试主要关注集成、API 和验收。
此类测试的示例
<?php // Model use Illuminate\Database\Eloquent\Model; class Person extends Model { } // Repository use Illuminate\Support\Collection; class PersonRepository { public function findPerson(int $id): ?Person { return Person::find(1); } } // Test class PersonRepositoryTest extends TestCase { public function testFindPerson() { factory(Person::class)->create(['id' => 1]); $person = $this->app->make(PersonRepository::class)->findPerson(1); $this->assertInstanceOf(Person::class, $person); $this->assertSame(1, $person->id); } }
从我们的示例来看,这是一个 集成 测试,而不是单元测试。为什么?我们将通过以下列表来回答
- 单元测试是系统中单个单元的测试,这些是你的方法和函数。
- 如果可能的话,任何从你的类中出去的函数都必须以模拟的形式。
- 如果你进行依赖注入,这是可能的
- 如果你遵循 SOLID,这也是可能的
- 单元测试不应调用任何第三方服务或系统外部的服务。
从这个列表和示例来看,我们的测试调用了数据库,如果你从模型中调用 find,这将立即使用你的连接执行查询。
哦!为什么不用 sqlite,因为 sqlite 只是一个基于文件的数据库,可以在不使用其他数据库的情况下使用?答案是不要为了满足单元测试而使用 sqlite,为什么?
- 使用 sqlite 仍然被视为你的类之外,仍然可以被视为第三方服务。
- 并非所有 SQL 查询都能处理我的 sqlite,每个数据库驱动程序都有不同的语法,特别是如果你使用 NoSQL。
类和特性
如何使用
安装
composer require cydrickn/laravel-query-factory
注册提供者
配置可以在 config/app.php
中找到。
要注册你的提供者,将其添加到数组中
'providers' => [ // Other Service Providers LaravelQueryFactory\QueryFactoryProvider::class, ];
创建模型
模型将使用 LaravelQueryFactory\Models\Traits\QueryFactoryTrait
,这将使用外观,你也可以通过调用 setQueryFactory
来设置 QueryFactory。
将 QueryFactoryTrait
添加到你的模型中,这将用 QueryFactoryFacade
替换当前的查询生成器,这样你就可以模拟它。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use LaravelQueryFactory\Models\Traits\QueryFactoryTrait; class Person extends Model { use QueryFactoryTrait; protected $fillable = ['name', 'gender']; }
创建存储库/服务
创建存储库或服务只是你的正常服务。你可以选择是否注入 LaravelQueryFactory\QueryFactory
,但在这个例子中我们不会,因为我们使用默认的外观 QueryFactory。
人员存储库
<?php namespace App\Repository; use App\Models\Person; use Illuminate\Support\Collection; class PersonRepository { public function findById(): ?Person { return Person::find(1); } public function findByGender(string $gender): Collection { return Person::where('gender', '=', $gender)->get(); } }
创建单元测试
对于我们的单元测试,你需要使用 LaravelQueryFactory\Traits\MockQueryFactory
。这个特性会模拟 Laravel 使用的连接类,这样它就不会连接到任何类型的数据库。
mockConnection
函数接受以下参数
- mysql
- postgres
- sqlite
- sqlserver
为什么需要指定这些驱动程序?这样你就可以获取用于测试的生成查询,因为每个驱动程序在如何转换查询构建器方面都有不同的查询。但我建议你使用你的系统使用的驱动程序,这样你可以真正检查你的期望 SQL。
<?php namespace App\Tests\Unit; use LaravelQueryFactory\Traits\MockQueryFactory; use LaravelQueryFactory\Facades\QueryFactoryFacade; class PersonRepositoryTest extends TestCase { use MockQueryFactory; /** * Test by just using mock connection and QueryFacades * and you can assert the generated sql. */ public function testFindWithGeneratedSql() { $connection = $this->mockConnection('mysql'); $queryBuilder = Person::newQueryBuilder(); $queryBuilder->connection = $connection; QueryFactoryFacade::shouldReceive('createQueryBuilder')->andReturn($queryBuilder); $repository = new PersonRepository(); $repository->findById(1); $this->assertSame('select * from `people` where `people`.`id` = ? limit 1', $queryBuilder->toSql()); $this->assertSame([1], $queryBuilder->getBindings()); } /** * Test by mocking connection with result and QueryFacades */ public function testFindWithResult() { $connection = $this->mockConnection('mysql'); // Mock result from connection $connection->shouldReceive('select')->once()->andReturn([['id' => 1]]); $queryBuilder = Person::newQueryBuilder(); $queryBuilder->connection = $connection; QueryFactoryFacade::shouldReceive('createQueryBuilder')->andReturn($queryBuilder); $repository = new PersonRepository(); $person = $repository->findById(1); $this->assertInstanceOf(Person::class, $person); } /** * Test by mocking query builder */ public function testFindByGenderWithMockingQueryBuilder() { $connection = $this->mockConnection('mysql'); // Mocking Query Builder $queryBuilder = Mockery::mock(QueryBuilder::class); $queryBuilder->shouldReceive('getConnection')->andReturn($connection); $queryBuilder->shouldReceive('from')->with('people')->andReturnSelf(); $queryBuilder->shouldReceive('where')->once()->with('gender', '=', 'male')->andReturnSelf(); $queryBuilder->shouldReceive('get')->once()->with(['*'])->andReturnSelf(); $queryBuilder->shouldReceive('all') ->once() ->withNoArgs() ->andReturn([['id' => 1, 'gender' => 'male']]); QueryFactoryFacade::shouldReceive('createQueryBuilder')->andReturn($queryBuilder); $repository = new PersonRepository(); $persons = $repository->findByGender('male'); $this->assertSame(1, $persons->first()->id); } }
结论
使用此库(Laravel Query Factory),你现在可以模拟你的查询构建器。通过模拟连接,将模拟连接,这样它就不会连接到数据库。