cydrickn/laravel-query-factory

Query Factory 用于轻松模拟查询构建器,而无需从真实数据库发送查询

v1.0.1 2020-04-04 16:03 UTC

This package is auto-updated.

Last update: 2024-09-05 01:38:54 UTC


README

PHP from Packagist Software License ci Coverage Status

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),你现在可以模拟你的查询构建器。通过模拟连接,将模拟连接,这样它就不会连接到数据库。