Mockando funções do PHP

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.

4 Responses to Mockando funções do PHP

  1. Diogo Santos says:

    Achei que o código deste phpunit é muito confuso, não?!!
    Principalmente essa parte:
    $fileGetContents->expects($this->once())->method(‘file_get_contents’)->with($this->equalTo($urlEsperada))

    Mas poder fazer testes unitários no PHP é muito bom e extramamente necessário.

    Um Abraço!

  2. Pois é cara… quando a linguagem não coopera a gente tem mais é que se virar🙂

  3. Willian says:

    Mesmo não gostando da solução, prefiro usar namespaces para “mockar” funções. Mas acho que em 2008 os namespaces não existiam no PHP. Runkit seria uma solução para este problema …

    Na minha opinião, criar wrappers para classes só aumenta mais o código (que você deve manter depois) e cria acoplamentos desnecessários.

    É legal ver pessoas se preocupando com a qualidade do código. Continue, parabéns.

    Um abraço, Willian.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: