terça-feira, 1 de abril de 2008

Mock de classe no PHPUnit executa o construtor

O mock de classes no PHPUnit não funciona muito bem, o ideal, claro é mocar interfaces, mas quando isso não é possível é preciso estar ciente de que o construtor da classe mocada será executado e portanto o teste ficará um tanto falho.

É possivel identificar facilmente esse problema com o código abaixo. No caso mocarei a classe ClasseUtilizada para ser utilizada na classe ClasseASerTestada. Ao executar o teste é possível verifique que o echo dentro do construtor será executado erroneamente.
class ClasseUtilizada {   
function ClasseUtilizada() {
echo "\n[passou no construtor de ClasseUtilizada]\n";
}
public function fazAlgumaCoisa() {
return "uhuuu";
}
}
class ClasseASerTestada {
function ClasseASerTestada($in) {
echo "\n[" . $in->fazAlgumaCoisa() . "]\n";
}
}
class MeuTeste extends PHPUnit_Framework_TestCase {
/**
* @Test
*/
public function teste_com_classe() {
$mock = $this->getMock("ClasseUtilizada");
$mock->expects($this->once())
->method('fazAlgumaCoisa')
->will($this->returnValue(2));
new ClasseASerTestada($mock);
}
}
Uma forma de contornar isso é se aproveitar das características dinâmicas do PHP para simplesmente criar um Mock de uma classe que não existe. Mas pra isso é preciso informar no método getMock não só o nome de uma classe inexistente, como também a lista de métodos que a classe teria. Veja como ficaria abaixo:
    /**
* @Test
*/
public function teste_com_classe_inexistente_indicando_metodos() {
$mock = $this->getMock("ClasseUtilizadaMock", Array('fazAlgumaCoisa'));
$mock->expects($this->once())
->method('fazAlgumaCoisa')
->will($this->returnValue(4));
new ClasseASerTestada($mock);
}
Se por um acaso você não indicar o segundo parâmetro, que é um array com os métodos que a classe possui, um erro parecido com este será exibido: "Fatal error: Call to undefined method Mock_ClasseUtilizadaMock_4322e2fd :: fazAlgumaCoisa()".

O problema dessa solução é o caso em que a classe a ser utilizada possui verificação de tipo no recebimento dos parâmetros. Como a classe mock criada dinamicamente não é do tipo esperado, ocorrerá um erro. Veja um exemplo de como isso aconteceria abaixo:
class ClasseUtilizada {   
function ClasseUtilizada() {
echo "\n---->passou no construtor de ClasseUtilizada<-----\n";
}
public function fazAlgumaCoisa() {
return "uhuuu";
}
}
class ClasseASerTestadaComVerificacaoDeTipo {
function ClasseASerTestada(ClasseUtilizada $in) {
echo "\n[" . $in->fazAlgumaCoisa() . "]\n";
}
}
class MeuTeste extends PHPUnit_Framework_TestCase {
/**
* @Test
*/
public function teste_com_classe_inexistente_na_classe_com_verificacao_de_tipo() {
$mock = $this->getMock("ClasseUtilizadaMock", Array('fazAlgumaCoisa'));
$mock->expects($this->once())
->method('fazAlgumaCoisa')
->will($this->returnValue(5));
new ClasseASerTestadaComVerificacaoDeTipo($mock);
}
}
Nesse caso o erro exibido será "Argument 1 passed to ClasseASerTestadaComVerificacaoDeParametro :: ClasseASerTestadaComVerificacaoDeParametro() must be an instance of ClasseUtilizada"

Para esses casos não tem jeito, é preciso alterar as classes para que exista uma interface a ser mocada. Mas eu sugiro que não utilize a verificação de tipo nos parâmetros, caso contrário, seria melhor deixar de lado uma linguagem dinâmica como PHP e usar uma mais estática como Java.

Nenhum comentário:

Postar um comentário