用于测试的自动模拟依赖注入
injected Trait允许您轻松创建类,并模拟出所有依赖项以用于测试目的。
以下模式很常见:
A并通过构造函数传入其依赖项(服务对象)A的每个依赖项的测试,断言它们按预期调用。许多测试逻辑最终成为构建对象的样板。 injected Trait旨在完全删除这个样板,让您专注于重要的事情:测试本身。
使用composer获取最新版本(您可能只需要它来进行开发):
$ composer require sellerlabs/ injected --dev
假设我们正在开发一个网络应用程序,并且我们希望在用户注册时向他们发送电子邮件。举一个简单的例子,我们假设用户完全由他们的电子邮件地址定义。当他们注册时,我们自然要向他们发送一封感谢电子邮件。此外,我们想测试电子邮件是否确实被发送,而不是实际发送。
首先,我们定义一个电子邮件服务:
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依赖项,并在我们的(非常简单的)注册过程中使用它。
为了测试这个类,我们必须做以下两件事之一:
Mockery之类的东西模拟EmailService对象,并确保使用预期的参数调用email 。 injected Trait可以让你轻松实现选项 2。让我们看一下:
use SellerLabs injected injected Trait ;
/**
* Class injected Example
*
* // 1. These are helpful annotations for IDEs and language tools
* @property MockInterface $service
* @method UserController make()
*
* @author Benjamin Kovach <[email protected]>
*/
class injected Example extends PHPUnit_Framework_TestCase
{
// 2. Use our trait
use injected Trait;
// 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 protected] ' ;
// 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 );
}
}每个使用injected Trait类都需要具有$className属性,该属性用于定位正在测试的类。 injected Trait提供了一个公共方法make ,它构造这种类型的对象,但模拟其依赖项并将它们作为属性保存到测试类本身。
因此,在testSignUp中,我们使用make()构建控制器,这使我们能够访问名为$service模拟EmailService类型对象。这是因为它是在UserController的构造函数中这样定义的:
public function __construct ( EmailService $ service )
{
$ this -> service = $ service ;
}在测试用例的持续时间内, $service成员变量绑定到这个模拟的EmailService ,这使我们能够对调用控制器的signUp方法时发生的情况做出预期。我们使用Mockery来创建模拟对象。类注释中有一些注释,有助于 IDE 自动完成这些类,因为模拟属性是动态声明的。
这个例子位于tests/ injected Example.php中。随意闲逛吧!
此特性的影响可能看起来相对较小,但在处理类具有多个依赖项的大型应用程序时,这使得测试变得更加容易。