Mockando funções do PHP

July 2, 2008

O phpunit é um framework para testes unitários em PHP. Esse framework inclui funcionalidades para utilização de mocks. No entanto, temos uma limitação pois não é possível mockar funções do PHP. Nesse post vou mostrar um possível workaround para esta situação.

Vamos supor o código abaixo:

public function get($params) {

    // A partir dos $params passados, montamos uma $url

    return file_get_contents($url);
}

O código acima recebe um array de parâmetros. A partir dele, monta a url de um webservice REST. Por fim, faz um HTTP GET nessa url e retorna o xml recebido. A responsabilidade deste método é montar a url corretamente a partir dos parâmetros recebidos. Este é o comportamento que deve ser testado. O GET é feito pela função file_get_contents.

Idealmente queremos mockar a função file_get_contents. Mocks são usados com diversas finalidades. Neste exemplo, as duas principais motivações são: isolar o código de forma a só testar uma pequena unidade (montagem da url corretamente); e não fazer chamadas a um webservice real (por exemplo para não sobrecarregar um ambiente de produção ou porque o webservice não existe no ambiente de desenvolvimento.).

Já que não é possível mockar funções do PHP, como podemos mockar file_get_contents? Uma possível solução é criarmos uma classe cuja única responsabilidade é chamar esta função:

class FileGetContents {

    function file_get_contents($url) {
    	return file_get_contents($url);
    }

}

O código que queremos testar fica como abaixo. Note que uma instância de FileGetContents é injetada via construtor:

function ClienteHttp($fileGetContents) {
    $this->fileGetContents = $fileGetContents;
}

public function get($params) {
    // A partir dos $params passados, montamos uma $url

    return $this->fileGetContents->file_get_contents($url);
}

E podemos mockar a classe FileGetContents no nosso teste:

/**
 * @test
 */
public function deveria_chamar_webservice_com_url_correta() {
    $urlEsperada = "http://url.esperada";
    $fileGetContents = $this->getMock('FileGetContentsMock',array('file_get_contents'));
    $fileGetContents->expects($this->once())
    ->method('file_get_contents')->with($this->equalTo($urlEsperada))

    $clienteHttp = new ClienteHttp($fileGetContents);
    $parametros; // array de parâmetros que devem resultar na $urlEsperada
    $clienteHttp->get($parametros);
}

Alguns podem argumentar – corretamente – que a classe FileGetContents não é testada. Mas se levarmos em conta que ela simplesmente delega para uma outra função, a desvantagem não é tão significativa assim. Vale enfatizar a importância de que essa classe não tenha nenhum outro comportamento. Caso contrário, a falta de testes já não seria mais aceitável.

Advertisements