如何用phpunit模拟非抽象特征方法?

时间:2019-10-22 11:34:59

标签: php phpunit php-traits

我有一个具有非抽象方法的特征(由另一个特征使用),我需要进行模拟,但是当我尝试这样做时,我得到了错误消息:

  

尝试配置方法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's documentation for testing traits

中显示的示例

为什么会收到此错误消息,如何模拟getOfficeDays才能测试我的isLate方法?

1 个答案:

答案 0 :(得分:1)

未被嘲笑的方法不能期望

我将在下面进行详细介绍,

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

不模拟任何方法,例如$mock等效于:

class Testing extends PHPUnitsInternalMockStub
{
    use EmployeeAttendenceTrait;
}

$mock = new Testing;

检查错误消息

错误消息显示为:

  

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

让我们(几乎依次)看看每种可能性

它不存在

代码看起来显然具有名为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 {

方法签名显然不是declare the method as final-并不是那样。

或为静态

public function getOfficeDays(): array {

方法签名很明显is also not static-因此也不是。

因为它不存在

通过逻辑推论,这已经成为问题-这是唯一需要对使用phpunit有所了解的问题。

docs for getMockForTrait阅读(强调):

  

getMockForTrait()方法返回使用指定特征的模拟对象。 模拟具有给定特征的所有抽象方法。这样可以测试特征的具体方法。

由于所有trait方法都不是抽象的,因此不应使用问题中的语法来模拟任何方法。更深入地了解,method signature for 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));
  }
}

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