2017-11-24 11 views
1

내 단위 테스트에서 메서드에 전달 된 Closure 함수가 정확히 한 번 호출되었는지 확인하려는 방법을 찾으려고합니다. . 하지만 PHP에서 Closure 클래스는 final로 선언되고이를 실행할 때 다음 오류 메시지가 나타납니다. "Class"Closure "가"final "로 선언되어 조롱을받을 수 없습니다."PHP 클로저가 함수에 매개 변수로 전달 된 것을 테스트하는 방법은 정확한 횟수로 호출되었습니다.

다음은 코드 예제입니다. 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); 
    } 
} 
+0

[phpunit을 함께 폐쇄을 조롱에 대한 흥미로운 제안 (https://github.com/sebastianbergmann/phpunit-mock-objects/issues/257) –

+0

나는 몰랐다' 클로저'는 흥미 롭다. 당신이 시도 할 수있는 한 가지 방법은'Closure' 타입 힌트를 제거하고 그 메소드의 시작 부분에'! is_callable()'과 같은 것을 기반으로하는 PHP 테스트를 추가하는 것입니다. 조롱 된 클래스가 호출 할 수있는 방법으로 놀아야 만합니다. (아마도 호출 마법을 구현하는 무언가의 일부가 아닌가? 여기에서 플레이해야 할 것입니다 ...) – halfer

+0

답변 해 주셔서 감사합니다. 메소드 스펙에서 Closure를 제거하면 확실히 문제가 해결됩니다. 그러나 우회입니다. 나는 누군가가 솔직한 해결책을 알고 있는지 궁금해했다. – andrew

답변

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 mixed 
    */ 
    public function remember($key, Closure $valueProducer) 
    { 
      if (array_key_exists($key, $this->storage)) 
      { 
        return $this->storage[$key]; 
      } 
      // store $valueProducder Closure 
      $this->storage[$key] = $valueProducer; 

    } 

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

      // check if $key has been called 
      // if true returns false 
      if(in_array($key,$this->called)) 
       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'); 
    } 
} 
+0

그것의 함수 이름 그래서 어떤 효과도 없어 나는 그가 제출 한 코드를 복사했다. 나는 이름을 바꾸는 것을 괴롭히지 않았다. – azjezz

+0

아, 그 정도면 충분하다. 또 다른 메모에서, 당신이 당신의 제안에 대한 요약을 당신의 대답에 추가 할 수 있습니까? 우리는 단순히 붙여 넣을 것이 아닌 해결책을 설명하는 대답을 권장합니다. 그것은 미래의 독자들에게 더 많은 도움이됩니다. – halfer

+1

업데이트 됨, 이제 Coluser를 두 번 호출하면 false가 반환됩니다. – azjezz