silasyudi/inject-mocks

通过#InjectMocks和#Mock注解自动将模拟对象注入测试对象,以加快使用PHPUnit进行单元测试的速度。

v3.0.0 2024-06-20 14:29 UTC

This package is auto-updated.

Last update: 2024-09-20 14:58:22 UTC


README

Tests Maintainability Test Coverage

通过#InjectMocks和#Mock注解自动将模拟对象注入测试对象,以加快使用PHPUnit进行单元测试的速度。

摘要

语言 / Idioma

阅读葡萄牙语版本 🇧🇷 这里.

安装

在开发环境中安装

composer require --dev silasyudi/inject-mocks

需求

  • PHP 8.3+
  • Composer 2

功能

在测试类中使用#InjectMocks和#Mock注解可以自动将模拟对象注入测试对象。

在典型场景中,我们会这样做

无#InjectMocks/#Mock示例

class SomeTest extends \PHPUnit\Framework\TestCase
{
    public void testSomething() 
    {
        $someDependency = $this->createMock(Dependency::class);    
        $anotherDependency = $this->createMock(AnotherDependency::class);
        ...
        $subject = new Service($someDependency, $anotherDependency, ...);
        ...    
    }
    
    ...

这种方法会带来维护的困难,因为如果测试对象发生变化,无论是增加、减少还是替换依赖项,你都需要在每个测试中更改它。

有了#InjectMocks/#Mock注解,我们抽象了这些测试对象的更改。示例

有#InjectMocks/#Mock示例

use SilasYudi\InjectMocks\InjectMocks;
use SilasYudi\InjectMocks\Mock;
use SilasYudi\InjectMocks\MockInjector;

class SomeTest extends \PHPUnit\Framework\TestCase
{
    #[Mock]
    private Dependency $someDependency;
    #[Mock]
    private AnotherDependency $anotherDependency;
    
    ...
    
    #[InjectMocks]
    
    public function setUp() : void 
    {
        MockInjector::inject($this);
    }
    
    public void testSomething()
    {
        // $this->subject e as dependências já estão instanciadas.
    }
    
    ...

用法

如前一个主题中的示例,#InjectMocks属性必须放在你想要测试的测试对象属性上,#Mock属性必须放在你想要模拟或注入的对应依赖项的属性上。

之后,运行注入器服务,使用语句MockInjector::inject($this)。这个执行可以声明在每个测试或setUp中。

执行注入器后,带有#InjectMocks注解的service将是一个在测试类作用域中的真实实例,每个带有#Mock注解的依赖项将是一个MockObject实例,通过构造函数注入到测试对象中,并且也将可在测试类作用域中使用。

详细信息

1. 属性范围

  • 两者#InjectMocks和#Mock都必须放在TestCase类中的TYPED属性上;
  • 带有#InjectMocks和#Mock属性的属性必须是对象;
  • 每个TestCase只能使用一个#InjectMocks属性。当在同一作用域中使用多个时,此库将仅使用第一个,并忽略其他;
  • 你必须为每个你想要模拟的测试对象依赖项使用一个#Mock属性;
  • 在未类型化的属性或原始类型上使用属性将导致MockInjectException异常。
  • 当在同一测试类中使用相同类型的多个对象时,此库将通过属性名称匹配每个对象,这些名称必须与测试对象类相同。

2. 行为

#InjectMocks和#Mock可以独立使用,也可以一起使用。每个的详细信息

2.1. #InjectMocks

它将通过构造函数创建一个真实实例,如果有构造函数参数,则以下值将按顺序用于每个参数:

  • 如果存在,则从#[Mock]属性创建的mock
  • 如果它是可选参数,则使用default值;
  • 如果它被类型化为null,则使用null
  • 如果它不是原始类型,则创建一个mock。在这种情况下,此mock不会注入到TestCase作用域中;
  • 如果前面的选项都不满足,则将抛出MockInjectException异常。

注意:你可以在测试对象的所有、一些或没有任何依赖项上使用#Mock属性。

2.2. #Mock

将创建一个注入到TestCase作用域的mock,而不使用构造函数。这种行为与TestCase::createMock()相同。