fundo
Eu tenho usado o Laravel 4 recentemente e parte da minha diversão em usar este framework são os utilitários de teste disponíveis para desenvolvedores usarem. Depois de entrar na metodologia TDD, comecei a procurar as melhores práticas em relação a testes no Laravel. Dois dos recursos mais úteis que encontrei são o tutorial de Jeffrey Way sobre Testando Controladores do Laravel e Philip Brown’s How to structure testable Controllers in Laravel 4
. Para esta dica, usaremos o artigo de Jeffrey como referência principal, mas sugiro que você também dê uma olhada na postagem de Philip. Para os exemplos abaixo, faremos referência aos exemplos de Jeffrey por uma questão de continuidade.
Continuação
Depois de discutir como configurar seu primeiro teste de controlador, até a criação de repositórios para simular o banco de dados, Jeffrey terminou seu tutorial com a simulação do modelo do Eloquent. Agora, tentaremos desacoplar a lógica de teste do modelo.
<?php
## app/models/Post.php
class Post extends Eloquent {
public static function shouldReceive()
{
$class = get_called_class();
$repo = "Way\Storage\{$class}\{$class}RepositoryInterface";
$mock = Mockery::mock($repo);
App::instance($repo, $mock);
return call_user_func_array(
[$mock, 'shouldReceive'], func_get_args());
}
}
Dado o exemplo de Jeffrey acima, o propósito de adicionar shouldReceive()
aos seus modelos do Eloquent é tornar seus testes mais legíveis. No entanto, como ele diz, “Devo incorporar a lógica de teste ao código de produção?” Existe outra maneira de removermos a lógica de teste e é por meio da implementação de fachadas.
Criando a fachada
A razão para criar uma fachada para nosso repositório é que as fachadas foram criadas principalmente para teste. Então, em vez de criarmos os objetos fictícios nós mesmos, podemos chamar a fachada que criamos e simular isso. Lembre-se, é inerente às fachadas, então vamos usar isso a nosso favor. Primeiro, vamos remover a função de nosso modelo e, em seguida, criar nossa fachada:shouldReceive()
shouldReceive()
<?php namespace WaySupportFacades;
# app/lib/Way/Support/Facades/Post.php
use IlluminateSupportFacadesFacade;
class Post extends Facade {
protected static function getFacadeAccessor()
{
return 'WayStoragePostPostRepositoryInterface';
}
}
Para tornar esta fachada mais simples de usar, vamos adicionar um alias para ela. Existem duas maneiras de fazermos isso. Uma é adicioná-lo em nossa fachada ao alias
array no arquivo de configuração. Essa é uma abordagem direta, mas prefiro centralizar nosso alias em nosso provedor de serviços. Dessa forma, podemos encontrar facilmente todos os apelidos associados às nossas fachadas. Obrigado, Chris Fidao , pelo snippet!app/config/app.php
<?php namespace WayStorage;
# app/lib/Way/Storage/StorageServiceProvider.php
use IlluminateSupportServiceProvider;
class StorageServiceProvider extends ServiceProvider {
// Triggered automatically by Laravel
public function register()
{
$this->app->bind(
'WayStoragePostPostRepositoryInterface',
'WayStoragePostEloquentPostRepository'
);
$this->app->booting(function()
{
$loader = IlluminateFoundationAliasLoader::getInstance();
$loader->alias('Post', 'WaySupportFacadesPost');
});
}
}
Observe que nosso modelo e fachada têm os mesmos nomes de classe. Para consistência com as convenções de nomenclatura, vamos refatorar nosso modelo para PostModel
; não se esqueça de renomear as referências do modelo EloquentPostRepository
também. Observe, entretanto, que isso não segue as convenções de nomenclatura do Eloquent, então você pode ter que substituir alguns argumentos especialmente para relacionamentos de modelo.
No entanto, ainda não terminamos. Nosso controlador agora será injetado com a fachada em vez do modelo, e não precisamos mais disso, pois a fachada deve cuidar disso para nós. Abaixo está o modificado PostsController
, que não depende mais do Post
a ser injetado no construtor:
<?php
# app/controllers/PostsController.php
class PostsController extends BaseController {
public function index()
{
$posts = Post::all();
return View::make('posts.index', ['posts' => $posts]);
}
public function store()
{
$input = Input::all();
// We'll run validation in the controller for convenience
// You should export this to the model, or a service
$v = Validator::make($input, ['title' => 'required']);
if ($v->fails())
{
return Redirect::route('posts.create')
->withInput()
->withErrors($v->messages());
}
Post::create($input);
return Redirect::route('posts.index')
->with('flash', 'Your post has been created!');
}
}
Testando
Como Jeffrey já usou para seus testes, não há necessidade de modificarmos nossos testes. Simplesmente mudamos a referência de um modelo para uma fachada.shouldReceive()
Post
É isso aí! Espero que essa abordagem funcione para você, especialmente se estiver preocupado com a flexibilidade máxima de sua interface.