如何使用PHPUnit模拟非抽象Trait方法?

6

我有一个特性(被另一个特性使用),其中有一个非抽象方法需要我模拟,但是当我尝试这样做时,会收到以下错误消息:

正在尝试配置无法配置的方法getOfficeDays,因为它不存在,还未指定,是最终的,或者是静态的。

这是我正在尝试测试的代码:

trait OfficeDaysTrait {
  public function getOfficeDays(): array {
    // This normally calls the database, that's why I want to mock it in tests
    return [
     [
       'day' => 'mon',
       'openingTime' => '08:00:00',
       'closingTime' => '17:00:00'
     ]
    ];
  }
}

trait EmployeeAttendenceTrait {
  use OfficeDaysTrait;

  public function isLate($today)
  {
     $late = false;
     $officeDays = $this->getOfficeDays();

     // compare logic with today.. whether the employee is late or not

     return $late;
  }
}

这是我的测试方法体:

$mock = $this->getMockForTrait(EmployeeAttendenceTrait::class); 

$mock->expects($this->once())
 ->method('getOfficeDays')
 ->will([])
;

$this->assertTrue($mock->isLate());


这个语法是基于phpunit的测试traits文档中展示的示例。
为什么我会收到这个错误消息,如何模拟getOfficeDays以便测试我的isLate方法?

我已经更新了问题,聚焦于问题本身,并添加了文档的参考,希望能够更好地为未来的读者提供帮助 - 如果您有任何建议,请随意提出。非常好的问题,做得很好 :)。 - AD7six
嘿@AD7six,你花了很多时间修改问题并给出了精确的答案和详细的解释。非常感谢你。真的很感激。 - Md. Kausar Alam
1个回答

12

未被模拟的方法无法有期望

稍后我会详细介绍,但是这个:

$mock = $this->getMockForTrait(EmployeeAttendenceTrait::class); 

如果没有模拟方法,那么$mock就相当于:

class Testing extends PHPUnitsInternalMockStub
{
    use EmployeeAttendenceTrait;
}

$mock = new Testing;

检查错误信息

错误信息如下:

尝试配置无法配置的方法getOfficeDays,因为它不存在、未指定、是final或是static

让我们逐个可能性来看(几乎是按顺序)

不存在

代码明显有一个名为getOfficeDays的方法 - 如果不确定,使用get_class_methods可以澄清:

<?php

use PHPUnit\Framework\TestCase;

class SOTest extends TestCase
{

  public function testMethods()
  {
    $mock = $this->getMockForTrait(EmployeeAttendenceTrait::class);
    print_r(get_class_methods($mock));
  }
}

这将输出:

R                                                                   1 / 1 (100%)Array
(
    [0] => __clone
    [1] => expects
    [2] => method
    [3] => __phpunit_setOriginalObject
    [4] => __phpunit_getInvocationMocker
    [5] => __phpunit_hasMatchers
    [6] => __phpunit_verify
    [7] => isLate
    [8] => getOfficeDays
)


Time: 569 ms, Memory: 12.00MB

There was 1 risky test:

1) SOTest::testMethods
This test did not perform any assertions

所以不是那个。

最终确定

public function getOfficeDays(): array {

该方法签名明显没有将方法声明为final,所以不是那个问题。

或者是静态的

public function getOfficeDays(): array {

方法签名明显也不是静态的 - 所以也不是那个问题。

未指定

通过逻辑推断,这就是问题所在 - 这是唯一需要对使用phpunit有些了解的问题。 getMockForTrait的文档(已加重)如下:

getMockForTrait() 方法返回一个使用指定 trait 的模拟对象。给定 trait 的所有抽象方法都将被模拟。这允许测试 trait 的具体方法。

由于没有任何 trait 方法是抽象的,因此不希望使用问题中的语法模拟任何方法。更深入地查看getMockForTrait的方法签名,会发现它有更多参数:
    /**
     * Returns a mock object for the specified trait with all abstract methods
     * of the trait mocked. Concrete methods to mock can be specified with the
     * `$mockedMethods` parameter.
     *
     * @throws RuntimeException
     */
    public function getMockForTrait(
        string $traitName, 
        array $arguments = [], 
        string $mockClassName = '', 
        bool $callOriginalConstructor = true, 
        bool $callOriginalClone = true, 
        bool $callAutoload = true, 
        array $mockedMethods = [], 
        bool $cloneArguments = true): MockObject

有一个参数用于指定显式模拟的方法,因此可以这样编写测试:

class SOTest extends TestCase
{

  public function testMethods()
  {
    $mock = $this->getMockForTrait(
      EmployeeAttendenceTrait::class, 
      [],   # These
      '',   # are
      true, # the
      true, # defaults
      true, #
      ['getOfficeDays']
    );

    $mock->expects($this->once())
     ->method('getOfficeDays')
     ->willReturn([]);

    $today = new DateTime(); // or whatever is appropriate
    $this->assertTrue($mock->isLate($today));
  }
}

将生成一个有效的模拟对象并允许测试运行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接