sellerlabs/injected

自动模拟注入

v1.0.0 2018-10-06 23:53 UTC

This package is not auto-updated.

Last update: 2024-09-22 03:22:28 UTC


README

自动模拟依赖注入以进行测试

这是什么?

InjectedTrait 允许您轻松创建具有所有依赖项模拟的类,用于测试目的。

为什么?

以下模式非常常见

  1. 创建一个类 A,其依赖项(服务对象)通过构造函数传入
  2. 在对象的内部各种函数中使用这些服务
  3. 创建测试来模拟 A 的每个依赖项,断言它们按预期调用。

大量的测试逻辑最终变成了构建对象时的样板代码。 InjectedTrait 旨在完全删除这些样板代码,让您专注于真正重要的事情:测试本身。

入门

使用 composer 获取最新版本(您可能只需要在开发中使用它)

$ composer require sellerlabs/injected --dev

示例用法

假设我们正在开发一个Web应用程序,并且当用户注册时想要向他们发送电子邮件。为了简单起见,让我们假设用户完全由他们的电子邮件地址定义。当他们注册时,我们自然希望发送感谢电子邮件。此外,我们希望测试电子邮件是否真的被发送了 而不实际发送它们

首先,让我们定义一个电子邮件服务

class EmailService
{
    public function email($address, $content)
    {
        // Send an email to $address with body $content
    }
}

(在实际应用程序中,email 会发送电子邮件 -- 我们这里不关心实现细节!)

让我们还定义一个 UserController,它处理非常简单的注册过程

class UserController
{
    private $service;

    public function __construct(EmailService $service)
    {
        $this->service = $service;
    }

    public function signUp($emailAddress)
    {
        $this->service->email($emailAddress, 'Thanks for signing up!');

        return $emailAddress;
    }
}

在这里,我们通过构造函数提供 EmailService 依赖项,并在我们的(极其简单)注册过程中使用它。

为了测试这个类,我们必须做以下两件事之一

  1. 实际发送一封电子邮件,并确保以某种方式发送了,或者
  2. 使用类似 Mockery 的工具模拟 EmailService 对象,并确保 email 以预期的参数被调用。

InjectedTrait 允许您轻松实现选项2。让我们看看

use SellerLabs\Injected\InjectedTrait;

/**
 * Class InjectedExample
 *
 * // 1. These are helpful annotations for IDEs and language tools
 * @property MockInterface $service
 * @method UserController make()
 *
 * @author Benjamin Kovach <benjamin@roundsphere.com>
 */
class InjectedExample extends PHPUnit_Framework_TestCase
{
    // 2. Use our trait
    use InjectedTrait;

    // 3. Provide the name of the class to test
    protected $className = UserController::class;

    public function testSignUp()
    {
        // 4. Make a controller with mocked dependencies
        $controller = $this->make();
        $address = 'email@test.me';

        // 5. We can access any mocked dependency of the class as a property
        $this->service->shouldReceive('email')
            ->withArgs(
                [
                    $address,
                    'Thanks for signing up!'
                ]
            );

        $result = $controller->signUp($address);

        $this->assertEquals($address, $result);
    }
}

使用 InjectedTrait 的每个类都必须有一个 $className 属性,该属性用于定位正在测试的类。 InjectedTrait 提供一个公共方法,make,该方法构建此类类型的对象,但模拟其依赖项并将其作为属性保存到测试类本身。

因此,在 testSignUp 中,我们使用 make() 构建控制器,这使我们能够访问一个名为 $service 的模拟 EmailService 类型对象。这是因为它在 UserController 的构造函数中这样定义的。

public function __construct(EmailService $service)
{
    $this->service = $service;
}

在测试用例期间,$service 成员变量绑定到这个模拟的 EmailService,这允许我们预测当控制器中的 signUp 方法被调用时它会发生什么。我们使用 Mockery 来创建模拟对象。类注释中有一些注释,有助于IDE自动完成这些类,因为模拟属性是动态声明的。

这个例子在 tests/InjectedExample.php 中。请随意探索!

此特性的影响可能看起来相对较小,但在大型应用程序中,当类有多个依赖项时,这使得测试变得 容易得多