【问题标题】:How to test that a PHP Closure passed as a parameter to a function has been called exact number of times如何测试作为参数传递给函数的 PHP 闭包是否已被准确调用次数
【发布时间】:2017-11-24 19:00:54
【问题描述】:

我正在尝试找到一种方法来检查传递给我的单元测试中的方法的闭包函数是否被准确地调用了一次。但是 PHP 中的 Closure 类被声明为 final,当我运行它时我收到以下错误消息:“Class "Closure" is declared "final" and cannot be mocked."

这是一个代码示例。我正在尝试检查 valueProducer() 是否被调用了一次。

class PoorCache{
    protected $storage;

    /**
     * Returns value from cache and if the value lacks puts it into the cache storage
     * @param string $key
     * @param Closure $valueProducer produces a value for storage, i.e. makes a request to DB
     * @return mixed
     */
    public function remember($key, Closure $valueProducer)
    {
            if (array_key_exists($key, $this->storage))
            {
                    return $this->storage[$key];
            }

            $this->storage[$key] = $valueProducer();
            return $this->storage[$key];
    }
}

class PoorCacheTest extends TestCase {
    public function testRemeber(){
        $mockedValueProducer = $this->getMock(\Closure::class);
        $mockedValueProducer->expects($this->once())->method('call');

        $cache = new PoorCache();
        $cache->remember('myKey', $mockedValueProducer);
        $cache->remember('myKey', $mockedValueProducer);
    }
}

【问题讨论】:

  • 我不知道Closurefinal,很有趣。您可以尝试的一件事是删除Closure 类型提示,并在该方法的开头添加基于!is_callable() 之类的PHP 测试。您必须尝试模拟类如何变得可调用(可能是对实现调用魔法的东西的部分模拟?必须在这里玩......)
  • 感谢您的回答。从方法规范中删除闭包肯定会解决问题。但它是一个旁路。我想知道是否有人知道简单的解决方案。

标签: php unit-testing testing mocking


【解决方案1】:

您可以创建一个不存在的类 double 来实现 __invoke 方法。但 double 不会是 \Closure 的实例,因此您必须删除 remember 方法的 \Closure 类型提示。

// Create double
$double = $this->getMockBuilder('NonExistentClass')
    ->setMethods(['__invoke'])
    ->getMock();

// Set expectations
$double->expects($this->once())->method('__invoke');

// Test
$double();

【讨论】:

  • 很好的答案 - 关键是记住魔术方法 __invoke。
  • 如果有人想作为可调用的方法传递,[$double, '__invoke']
【解决方案2】:

不是存储给定闭包的结果,而是存储闭包,然后调用一次。

像这样:

class PoorCache {

    protected $storage;
    private $called = [];

    /**
     * Returns value from cache and if the value lacks puts it into the cache storage
     * @param string $key
     * @param Closure $valueProducer produces a value for storage, i.e. makes a request to DB
     * @return null
     */
    public function remember($key, Closure $valueProducer)
    {
            // store $valueProducder Closure
            $this->storage[$key] = $valueProducer;

            if(isset($this->called[$key])) {
                unset($this->called[$key]); 
            }
            return null;
    }

    // call producer closure by key 
    public function callProducer($key)
    {
        if(isset($this->storage[$key]))  {

           // check if $key has been called 
           // if true returns false
           if(isset($this->called[$key])) {
                return false;
           }

           // add $key to called array 
           $this->called[] = $key;
           // call closure
           return ($this->storage[$key])();
        }
    }
}

class PoorCacheTest extends TestCase {

    public function testRemeber(){
        $mockedValueProducer = $this->getMock(\Closure::class);
        $mockedValueProducer->expects($this->once())->method('call');

        $cache = new PoorCache();

        // store closure once 
        $cache->remember('myKey', $mockedValueProducer);

        // store closure twice 
        // this is going to override the own before it 
        // since they both have the same key 
        $cache->remember('myKey', $mockedValueProducer);

        // call producer once 
        $cache->callProducer('myKey');

        // call producer twice
        // NOTE: this going to return false since 'myKey' 
        // has already been called 
        $cache->callProducer('myKey');
    }
}

【讨论】:

  • 它是一个函数名,所以它没有任何影响我也复制了他提交的代码我没有费心更改名称
  • 啊,很公平。另一方面,您能否在答案中添加您所提议内容的摘要?我们鼓励解释解决方案的答案,而不仅仅是粘贴的东西。这使它对未来的读者更有帮助。
  • 已更新,现在调用 Coluser 两次将返回 false 第二次
  • 感谢您的回答。您建议的解决方案可以使我的示例工作。但是,我寻找了一种模拟方法参数中指定的Closure 的方法。我的代码示例只是此目的的背景。它没有实际价值,因此无需绕过Closure 的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-01-29
  • 1970-01-01
  • 2021-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多