【问题标题】:PHPUnit cannot mock __get function on mock objectPHPUnit 无法在模拟对象上模拟 __get 函数
【发布时间】:2017-08-25 14:02:08
【问题描述】:

我正在尝试为我的单元测试模拟一个 mysqli 对象,因此我必须模拟属性 mysqli_result::__get() 或直接模拟属性 mysqli_result::num_rows。 我已经在寻找解决方案,但我找到的答案不起作用。

我的代码如下所示:

use PHPUnit\Framework\TestCase;

class Mocks extends TestCase{
    public function makeMysqliMock(array $queries): mysqli{
        // build mocked mysqli-object
        #$link = $this
        #   ->getMockBuilder("mysqli")
        #   // set methods
        #   ->setMethods(['__get', "query", "real_escape_string"])
        #   ->getMock();
        $link = $this->createMock("mysqli");
        // set method 'real_escape_string'
        $link->expects($this->any())
            ->method("real_escape_string")
            ->will($this->returnCallback(function($str){
                return addslashes($str); 
            }));
        // set method 'query'
        $link->expects($this->any())
            ->method("query")
            ->will($this->returnCallback(function(string $query) use ($queries){
                // pick the query result for the current test
                $data = isset($queries[$query]) ? $queries[$query] : null;
                // build mocked 'mysqli_result'
                if (is_array($data)){
                    $result = $this
                        ->getMockBuilder("mysqli_result")
                        ->setMethods(["fetch_assoc", "__get"])
                        ->disableOriginalConstructor()
                        ->getMock();
                    // build simulated fetch method
                    $result->expects($this->any())
                        ->method("fetch_assoc")
                        ->withAnyParameters()
                        ->will($this->returnCallback(function() use ($data){
                            // set internal result pointer
                            static $index = 0;
                            // return current result - increment pointer
                            return isset($data[$index]) ? $data[$index++] : false;
                        }));
                    $result->expects($this->at(0))
                        ->method("__get")
                        ->with($this->equalTo("mysqli_num_rows"))
                        ->will($this->returnValue(count($data)));


                    return $result;
                }else {
                    return is_null($data) ? false : 1;
                }
            }));
        return $link;
    }
}

当我运行该代码时,phpunit 会给我以下错误消息:

C:\xampp\htdocs\werimanage\php>php phpunit-6.0.11.phar 测试 Sebastian Bergmann 和贡献者的 PHPUnit 6.0.11。

PHP 致命错误:方法 Mock_mysqli_result_d3aa5482::__get() 必须在 phar://C:/xampp/htdocs/werimanage/php/phpunit-6.0.11.phar/phpunit-mock-objects/Generator 中采用 1 个参数.php(263) : eval()'d 代码在第 53 行

致命错误:方法 Mock_mysqli_result_d3aa5482::__get() 必须在 phar://C:/xampp/htdocs/werimanage/php/phpunit-6.0.11.phar/phpunit-mock-objects/Generator 中采用 1 个参数。 php(263) : eval()'d 代码在第 53 行

所以我不知道错误是什么或如何解决它。我将衷心感谢您的帮助。谢谢!

【问题讨论】:

    标签: php unit-testing mocking phpunit


    【解决方案1】:

    根据 php 文档,方法 __get 需要 1 个参数(更多的是 phpUnit 返回的错误)。

    您可以在文档中看到:

    public mixed __get ( string $name )

    $name 参数是 与之交互的属性。

    我想您将在 sql 请求中为您的列调用 __get 方法。然后,您必须使用每个参数(每列)模拟 __get 方法的所有调用和返回。

    希望这会对你有所帮助。

    【讨论】:

      【解决方案2】:

      发生此错误是因为魔术方法__get 必须接受一个参数——属性名称。当 PHPUnit 生成代码来声明模拟时,它使用原始类中的方法声明。由于没有为mysqli_result 声明方法__get,PHPUnit 模拟生成器将其声明为__get(),不带参数,因此遇到错误。

      如果您真的想按照自己的方式进行编码和测试,可以使用以下方法:

      class SuperMock extends mysqli_result {
          public function __get($name){
      
          }
      }
      
      class Mocks extends PHPUnit_Framework_TestCase
      {
          public function testGet()
          {
              $data = ['one' => 1, 'two' => 2];
              $mock = $this->getMockBuilder('SuperMock')
                           ->disableOriginalConstructor()
                           ->setMethods(['__get', 'fetch_assoc'])
                           ->getMock();
              $mock->expects($this->once())
                   ->method('__get')
                   ->with('nonexistent')
                   ->willReturn(42);
              $mock->expects($this->once())
                   ->method('fetch_assoc')
                   ->willReturn($data);
              $this->assertInstanceOf('mysqli_result', $mock);
              $this->assertSame(42, $mock->nonexistent);
              $this->assertSame($data, $mock->fetch_assoc());
          }
      
          public function testDirectProperty(){
              $mock = $this->getMockBuilder('SuperMock')
                           ->disableOriginalConstructor()
                           ->setMethods(['__get', 'fetch_assoc'])
                           ->getMock();
              $mock->nonexistent = 42;
              $this->assertSame(42, $mock->nonexistent);
          }
      }
      

      这将从技术上解决问题。我仍然建议修改您的测试策略。因为现在看起来您正在测试如何执行与数据库的交互。但是您是否真的需要测试数据是否作为关联数组获取,并且该行数是通过mysqli_num_rows 属性计算出来的?采用这种方式会使测试与生产代码过度耦合。我相信,当测试检索到的结果而不是这个过程的内部细节更充分时,情况就是如此。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-09-12
        • 2017-01-13
        • 2022-08-22
        • 2013-02-13
        • 2010-09-25
        • 2021-10-31
        • 2012-09-26
        • 2011-10-28
        相关资源
        最近更新 更多