如何测试作为参数传递给函数的PHP Closure被称为确切次数

时间:2017-11-24 19:00:55

标签: php unit-testing testing mocking

我试图找到一种方法来检查传递给我的单元测试中的方法的Closure函数是否已被调用一次。但是PHP中的Closure类被声明为final,当我运行它时会收到以下错误消息:" Class" Closure"宣布"最终"并且不能被嘲笑。"

这是一个代码示例。我试图检查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);
    }
}

2 个答案:

答案 0 :(得分:1)

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

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

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

// Test
$double();

答案 1 :(得分:0)

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

像这样:

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');
    }
}