Posted on: October 09, 2023 04:43 PM
Posted by: Renato
Views: 264
API Laravel usando PHPUnit
A execução de testes unitários, de recursos automatizados e de endpoints de API são consideradas algumas das melhores práticas para garantir a implementação adequada de requisitos de software especificados, porque ajudam a garantir o sucesso de tais aplicativos. O teste, sem dúvida, tende a lhe dar 100% de garantia de que quaisquer alterações incrementais e recursos recém-implementados em seu projeto não danificarão o aplicativo. Essa prática costuma ser chamada de Desenvolvimento Orientado a Testes.
Laravel, como um dos frameworks PHP populares, foi construído com testes em mente e vem com um conjunto de testes chamado . PHPUnit é uma estrutura de teste construída para aumentar a produtividade dos desenvolvedores PHP durante o desenvolvimento. Ele foi projetado principalmente para testar código PHP nos menores componentes possíveis, conhecidos como testes unitários, mas também é flexível o suficiente para ser usado além dos testes unitários.
Neste tutorial, adotaremos uma abordagem de desenvolvimento orientado a testes e aprenderemos como testar os endpoints de um projeto de API Laravel. Começaremos escrevendo testes, esperando que falhem. Depois, escreveremos o código para fazer nossos testes passarem. Quando terminarmos, você terá aprendido como realizar testes básicos e estará confiante o suficiente para aplicar esse conhecimento em seus projetos de API Laravel novos ou existentes.
Pré-requisitos
Conhecimento básico de construção de aplicações com Laravel será útil neste tutorial. Além disso, você precisa garantir que instalou globalmente para gerenciar dependências.
Começando
Nossa API Laravel será usada para criar uma lista e exibir detalhes dos principais CEOs de tecnologia do mundo. Isso é semelhante ao que construímos em . Para começar o mais rápido possível, que contém as estruturas que permitem que nosso aplicativo funcione conforme especificado.
Para começar, execute o seguinte comando para baixar o projeto inicial usando :
$ git clone https://github.com/yemiwebby/laravel-api-testing-starter.git
Em seguida, vá para a pasta do novo projeto e instale todas as suas dependências:
// move into the new folder
$ cd laravel-api-testing-starter
//install dependencies
$ composer install
Este projeto de amostra já contém o seguinte:
- de usuário e CEO com respectivas
routes/api.php
arquivo. E finalmente, , , bem como uma rota de API contida no- instalado e configurado para funcionar com o projeto. Leia sobre como proteger a API Laravel.
Em seguida, crie um .env
arquivo na raiz do projeto e preencha-o com o conteúdo encontrado no .env.example
arquivo. Você pode fazer isso manualmente ou executando o comando abaixo:
$ cp .env.example .env
Agora gere a para este projeto com:
$ php artisan key:generate
Agora você pode executar o aplicativo php artisan serve
e prosseguir para para visualizar a página inicial:
Não há muito para ver aqui, pois esta é apenas uma página padrão para um projeto Laravel recém-instalado.
Configurando o banco de dados
Para começar a testar, você precisa configurar seu banco de dados de testes. Neste tutorial, simplificaremos as coisas usando um banco de dados SQLite na memória. Oferece a vantagem de maior velocidade para nossos scripts de teste.
Crie um test.sqlite
arquivo na database
pasta. Este arquivo será usado para interagir com nosso banco de dados de testes e manter uma configuração separada do banco de dados principal. Em seguida, substitua as variáveis de ambiente do banco de dados .env.testing
em seu .env
arquivo.
DB_CONNECTION=sqlite
DB_HOST=null
DB_PORT=null
DB_DATABASE=database/test.sqlite
DB_USERNAME=null
DB_PASSWORD=null
Cada teste requer TestCase
classe base localizada no tests/TestCase.php
arquivo e atualizando-a conforme mostrado abaixo: . Precisaremos executar migrações antes de cada teste para construir adequadamente o banco de dados para cada teste. Vamos configurar isso abrindo sua
<?php
namespace Tests;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Artisan;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, DatabaseMigrations;
public function setUp(): void
{
parent::setUp();
Artisan::call('passport:install');
}
}
Aqui incluímos a DatabaseMigrations
característica e adicionamos uma Artisan
chamada para instalar o passaporte.
Por último, use o seguinte comando para executar o PHPUnit a partir do terminal:
$ vendor/bin/phpunit
Você verá os resultados conforme mostrado abaixo:
PHPUnit 8.5.4 by Sebastian Bergmann and contributors.
.. 2 / 2 (100%)
Time: 2.03 seconds, Memory: 24.00 MB
OK (2 tests, 2 assertions)
A saída acima mostrou que dois testes foram executados com sucesso. Estes foram os testes padrão que vieram instalados com o Laravel. Faremos modificações em breve.
Para tornar o comando de execução do PHPUnit identificável, abra composer.json
o arquivo e adicione o comando de teste à seção de scripts conforme mostrado abaixo:
{
...
"scripts": {
...,
"test": [
"vendor/bin/phpunit"
]
}
}
Doravante, o comando test estará disponível como composer test
.
Criar fábrica de CEO
User
classe. Executaremos o seguinte comando para gerar um para a CEO
classe: no Laravel fazem uso da biblioteca PHP para gerar dados aleatórios de maneira conveniente para testes. Já que o Laravel vem pré-carregado com uma definição para
$ php artisan make:factory CEOFactory
Isso criará um novo arquivo nomeado CEOFactory.php
dentro da database/factories
pasta. Abra este novo arquivo e cole o seguinte conteúdo nele:
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\CEO;
use Faker\Generator as Faker;
$factory->define(CEO::class, function (Faker $faker) {
return [
'name' => $faker->name,
'company_name' => $faker->unique()->company,
'year' => $faker->year,
'company_headquarters' => $faker->city,
'what_company_does' => $faker->sentence
];
});
Especificamos os campos de nossa CEO
tabela e usamos a biblioteca Faker para gerar o formato correto de dados aleatórios para todos os campos.
Escrevendo nosso primeiro teste
Vamos começar a escrever nosso teste conforme mencionado anteriormente. Antes disso, exclua os dois arquivos de teste de exemplo nas pastas tests/Feature
e, tests/Unit
respectivamente.
Começaremos escrevendo um teste para o processo de autenticação. Isso inclui registro e login. Já temos um controlador criado para esse fim dentro da API
pasta. Então crie o AuthenticationTest
arquivo com:
$ php artisan make:test AuthenticationTest
Isso criará o AuthenticationTest.php
arquivo dentro da test/Feature
pasta. Abra o novo arquivo e substitua seu conteúdo por:
<?php
namespace Tests\Feature;
use App\User;
use Tests\TestCase;
class AuthenticationTest extends TestCase
{
public function testRequiredFieldsForRegistration()
{
$this->json('POST', 'api/register', ['Accept' => 'application/json'])
->assertStatus(422)
->assertJson([
"message" => "The given data was invalid.",
"errors" => [
"name" => ["The name field is required."],
"email" => ["The email field is required."],
"password" => ["The password field is required."],
]
]);
}
public function testRepeatPassword()
{
$userData = [
"name" => "John Doe",
"email" => "[email protected]",
"password" => "demo12345"
];
$this->json('POST', 'api/register', $userData, ['Accept' => 'application/json'])
->assertStatus(422)
->assertJson([
"message" => "The given data was invalid.",
"errors" => [
"password" => ["The password confirmation does not match."]
]
]);
}
public function testSuccessfulRegistration()
{
$userData = [
"name" => "John Doe",
"email" => "[email protected]",
"password" => "demo12345",
"password_confirmation" => "demo12345"
];
$this->json('POST', 'api/register', $userData, ['Accept' => 'application/json'])
->assertStatus(201)
->assertJsonStructure([
"user" => [
'id',
'name',
'email',
'created_at',
'updated_at',
],
"access_token",
"message"
]);
}
}
A partir do arquivo acima, os seguintes testes foram escritos:
testRequiredFieldsForRegistration
: Este teste garante que todos os campos obrigatórios para o processo de registro sejam preenchidos adequadamente.testRepeatPassword
: Isso obriga o usuário a repetir senhas. A senha repetida deve corresponder à primeira para que este teste seja aprovado.testSuccessfulRegistration
: aqui, criamos um usuário preenchido com dados fictícios para garantir que os usuários possam se inscrever com sucesso.
Agora use o seguinte comando para executar nosso teste usando PHPUnit:
$ composer test
Você verá os resultados abaixo:
> vendor/bin/phpunit
PHPUnit 8.5.4 by Sebastian Bergmann and contributors.
FFF 3 / 3 (100%)
Time: 259 ms, Memory: 18.00 MB
There were 3 failures:
1) Tests\Feature\AuthenticationTest::testRequiredFieldsForRegistration
Expected status code 422 but received 500.
Failed asserting that 422 is identical to 500.
/Users/yemiwebby/tutorial/twilio/testing/laravel-api-testing/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:185
/Users/yemiwebby/tutorial/twilio/testing/laravel-api-testing/tests/Feature/AuthenticationTest.php:14
2) Tests\Feature\AuthenticationTest::testRepeatPassword
Expected status code 422 but received 500.
Failed asserting that 422 is identical to 500.
/Users/yemiwebby/tutorial/twilio/testing/laravel-api-testing/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:185
/Users/yemiwebby/tutorial/twilio/testing/laravel-api-testing/tests/Feature/AuthenticationTest.php:34
3) Tests\Feature\AuthenticationTest::testSuccessfulRegistration
Expected status code 201 but received 500.
Failed asserting that 201 is identical to 500.
/Users/yemiwebby/tutorial/twilio/testing/laravel-api-testing/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:185
/Users/yemiwebby/tutorial/twilio/testing/laravel-api-testing/tests/Feature/AuthenticationTest.php:53
FAILURES!
Tests: 3, Assertions: 3, Failures: 3.
Script vendor/bin/phpunit handling the test event returned with error code 1
Isso é esperado, pois ainda não implementamos o recurso. Agora vamos escrever o código para fazer nosso teste passar. Abra app/Http/Controllers/API/Auth/AuthController.php
e use o seguinte conteúdo para isso:
<?php
namespace App\Http\Controllers\API\Auth;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;
class AuthController extends Controller
{
public function register(Request $request)
{
$validatedData = $request->validate([
'name' => 'required|max:55',
'email' => 'email|required|unique:users',
'password' => 'required|confirmed'
]);
$validatedData['password'] = bcrypt($request->password);
$user = User::create($validatedData);
$accessToken = $user->createToken('authToken')->accessToken;
return response([ 'user' => $user, 'access_token' => $accessToken, 'message' => 'Register successfully'], 201);
}
}
Agora corra composer test
. Neste ponto, nosso teste deve passar.
Teste o ponto final de login
Atualize o AuthenticationTest.php
arquivo adicionando mais métodos conforme mostrado aqui:
<?php
namespace Tests\Feature;
use App\User;
use Tests\TestCase;
class AuthenticationTest extends TestCase
{
...
public function testMustEnterEmailAndPassword()
{
$this->json('POST', 'api/login')
->assertStatus(422)
->assertJson([
"message" => "The given data was invalid.",
"errors" => [
'email' => ["The email field is required."],
'password' => ["The password field is required."],
]
]);
}
public function testSuccessfulLogin()
{
$user = factory(User::class)->create([
'email' => '[email protected]',
'password' => bcrypt('sample123'),
]);
$loginData = ['email' => '[email protected]', 'password' => 'sample123'];
$this->json('POST', 'api/login', $loginData, ['Accept' => 'application/json'])
->assertStatus(200)
->assertJsonStructure([
"user" => [
'id',
'name',
'email',
'email_verified_at',
'created_at',
'updated_at',
],
"access_token",
"message"
]);
$this->assertAuthenticated();
}
}
Aqui também criamos um teste para garantir que os campos obrigatórios não sejam deixados vazios pelo usuário que utiliza o testMustEnterEmailAndPassword()
método. Dentro do testSuccessfulLogin()
método, criamos um usuário fictício para verificar se o usuário foi autenticado com sucesso.
Agora podemos prosseguir e executar o teste novamente usando composer test
. Você adivinhou, isso irá falhar mais uma vez. Para garantir que o teste seja aprovado, atualize o AuthController.php
arquivo da seguinte maneira:
// app/Http/Controllers/API/Auth/AuthController.php
<?php
namespace App\Http\Controllers\API\Auth;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;
class AuthController extends Controller
{
...
public function login(Request $request)
{
$loginData = $request->validate([
'email' => 'email|required',
'password' => 'required'
]);
if (!auth()->attempt($loginData)) {
return response(['message' => 'Invalid Credentials']);
}
$accessToken = auth()->user()->createToken('authToken')->accessToken;
return response(['user' => auth()->user(), 'access_token' => $accessToken, 'message' => 'Login successfully'], 200);
}
}
No total, escrevemos cinco testes diferentes e importantes. Alguns dos casos testados incluem o status e a json()
estrutura da resposta da API. Na próxima seção, criaremos os conjuntos de testes para endpoints do CEO.
Escrevendo testes para os endpoints do CEO
Nesta seção, começaremos criando um novo arquivo de teste para hospedar os scripts de teste para os endpoints CEO. Use o seguinte comando para esse propósito:
$ php artisan make:test CEOTest
O comando anterior criará um novo arquivo de teste denominado CEOTest.php
arquivo dentro da tests/Feature
pasta. Abra-o e substitua seu conteúdo pelo seguinte:
<?php
namespace Tests\Feature;
use App\CEO;
use App\User;
use Tests\TestCase;
class CEOTest extends TestCase
{
public function testCEOCreatedSuccessfully()
{
$user = factory(User::class)->create();
$this->actingAs($user, 'api');
$ceoData = [
"name" => "Susan Wojcicki",
"company_name" => "YouTube",
"year" => "2014",
"company_headquarters" => "San Bruno, California",
"what_company_does" => "Video-sharing platform"
];
$this->json('POST', 'api/ceo', $ceoData, ['Accept' => 'application/json'])
->assertStatus(201)
->assertJson([
"ceo" => [
"name" => "Susan Wojcicki",
"company_name" => "YouTube",
"year" => "2014",
"company_headquarters" => "San Bruno, California",
"what_company_does" => "Video-sharing platform"
],
"message" => "Created successfully"
]);
}
public function testCEOListedSuccessfully()
{
$user = factory(User::class)->create();
$this->actingAs($user, 'api');
factory(CEO::class)->create([
"name" => "Susan Wojcicki",
"company_name" => "YouTube",
"year" => "2014",
"company_headquarters" => "San Bruno, California",
"what_company_does" => "Video-sharing platform",
]);
factory(CEO::class)->create([
"name" => "Mark Zuckerberg",
"company_name" => "FaceBook",
"year" => "2004",
"company_headquarters" => "Menlo Park, California",
"what_company_does" => "The world's largest social network",
]);
$this->json('GET', 'api/ceo', ['Accept' => 'application/json'])
->assertStatus(200)
->assertJson([
"ceos" => [
[
"id" => 1,
"name" => "Susan Wojcicki",
"company_name" => "YouTube",
"year" => "2014",
"company_headquarters" => "San Bruno, California",
"what_company_does" => "Video-sharing platform"
],
[
"id" => 2,
"name" => "Mark Zuckerberg",
"company_name" => "FaceBook",
"year" => "2004",
"company_headquarters" => "Menlo Park, California",
"what_company_does" => "The world's largest social network"
]
],
"message" => "Retrieved successfully"
]);
}
}
Isso pode parecer um pouco assustador, mas é semelhante aos testes que escrevemos anteriormente. Vamos decompô-lo. Criamos dois métodos diferentes:
testCEOCreatedSuccessfully
: Para testar se podemos criar um registro de CEO usando os dados apropriados.testCEOListedSuccessfully
: Aqui garantimos que a lista de CEOs criados possa ser recuperada e devolvida como resposta.
Ao contrário do AuthenticationTest
, o . CEOTest
foi escrito para endpoints protegidos por um chamado auth:api
. Para garantir que o usuário fictício recém-criado seja autenticado antes de acessar os endpoints, autenticamos $this->actingAs($user, 'api')
e autorizamos tal usuário a ter acesso para realizar qualquer uma das atividades CRUD (criar, ler, atualizar e excluir).
Agora execute o teste novamente usando composer test
. Você verá que ele falha novamente. Até agora, tenho certeza de que você entende por que isso não deu certo. Caso você ainda esteja aprendendo a lógica desses testes, precisamos preencher o CEOController.php
arquivo com o código apropriado para fazer o teste passar.
Atualizar o CEOController
Navegue até o app/Http/Controllers/API/CEOController.php
arquivo e use o seguinte conteúdo para ele:
<?php
namespace App\Http\Controllers\API;
use App\CEO;
use App\Http\Controllers\Controller;
use App\Http\Resources\CEOResource;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class CEOController extends Controller
{
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$data = $request->all();
$validator = Validator::make($data, [
'name' => 'required|max:255',
'company_name' => 'required|max:255',
'year' => 'required|max:255',
'company_headquarters' => 'required|max:255',
'what_company_does' => 'required'
]);
if($validator->fails()){
return response(['error' => $validator->errors(), 'Validation Error']);
}
$ceo = CEO::create($data);
return response([ 'ceo' => new CEOResource($ceo), 'message' => 'Created successfully'], 201);
}
public function index()
{
$ceos = CEO::all();
return response([ 'ceos' => CEOResource::collection($ceos), 'message' => 'Retrieved successfully'], 200);
}
}
Aqui, criamos métodos para armazenar os registros de um novo CEO e também recuperar a lista CEOs
do banco de dados respectivamente. Você pode executar o teste novamente e descobrir que desta vez ele foi aprovado.
Por último, vamos adicionar mais testes para recuperar, atualizar e também excluir os detalhes de um CEO específico. Abra o CEOTest.php
arquivo e adicione os seguintes métodos:
<?php
namespace Tests\Feature;
use App\CEO;
use App\User;
use Tests\TestCase;
class CEOTest extends TestCase
{
...
public function testRetrieveCEOSuccessfully()
{
$user = factory(User::class)->create();
$this->actingAs($user, 'api');
$ceo = factory(CEO::class)->create([
"name" => "Susan Wojcicki",
"company_name" => "YouTube",
"year" => "2014",
"company_headquarters" => "San Bruno, California",
"what_company_does" => "Video-sharing platform"
]);
$this->json('GET', 'api/ceo/' . $ceo->id, [], ['Accept' => 'application/json'])
->assertStatus(200)
->assertJson([
"ceo" => [
"name" => "Susan Wojcicki",
"company_name" => "YouTube",
"year" => "2014",
"company_headquarters" => "San Bruno, California",
"what_company_does" => "Video-sharing platform"
],
"message" => "Retrieved successfully"
]);
}
public function testCEOUpdatedSuccessfully()
{
$user = factory(User::class)->create();
$this->actingAs($user, 'api');
$ceo = factory(CEO::class)->create([
"name" => "Susan Wojcicki",
"company_name" => "YouTube",
"year" => "2014",
"company_headquarters" => "San Bruno, California",
"what_company_does" => "Video-sharing platform"
]);
$payload = [
"name" => "Demo User",
"company_name" => "Sample Company",
"year" => "2014",
"company_headquarters" => "San Bruno, California",
"what_company_does" => "Video-sharing platform"
];
$this->json('PATCH', 'api/ceo/' . $ceo->id , $payload, ['Accept' => 'application/json'])
->assertStatus(200)
->assertJson([
"ceo" => [
"name" => "Demo User",
"company_name" => "Sample Company",
"year" => "2014",
"company_headquarters" => "San Bruno, California",
"what_company_does" => "Video-sharing platform"
],
"message" => "Updated successfully"
]);
}
public function testDeleteCEO()
{
$user = factory(User::class)->create();
$this->actingAs($user, 'api');
$ceo = factory(CEO::class)->create([
"name" => "Susan Wojcicki",
"company_name" => "YouTube",
"year" => "2014",
"company_headquarters" => "San Bruno, California",
"what_company_does" => "Video-sharing platform"
]);
$this->json('DELETE', 'api/ceo/' . $ceo->id, [], ['Accept' => 'application/json'])
->assertStatus(204);
}
}
Os testes escritos acima foram usados para direcionar os registros de um determinado CEO
, passando um único id
como parâmetro para os api/ceo
endpoints usando os verbos HTTP apropriados (ou seja GET
, PATCH
, DELETE
).
A seguir, atualizaremos o CEOController
arquivo para garantir que os novos testes também sejam aprovados. Abra app/Http/Controllers/API/CEOController.php
e inclua os seguintes métodos:
<?php
namespace App\Http\Controllers\API;
use App\CEO;
use App\Http\Controllers\Controller;
use App\Http\Resources\CEOResource;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class CEOController extends Controller
{
...
/**
* Display the specified resource.
*
* @param \App\CEO $ceo
* @return \Illuminate\Http\Response
*/
public function show(CEO $ceo)
{
return response([ 'ceo' => new CEOResource($ceo), 'message' => 'Retrieved successfully'], 200);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\CEO $ceo
* @return \Illuminate\Http\Response
*/
public function update(Request $request, CEO $ceo)
{
$ceo->update($request->all());
return response([ 'ceo' => new CEOResource($ceo), 'message' => 'Updated successfully'], 200);
}
/**
* Remove the specified resource from storage.
*
* @param \App\CEO $ceo
* @return \Illuminate\Http\Response
* @throws \Exception
*/
public function destroy(CEO $ceo)
{
$ceo->delete();
return response(['message' => 'Deleted'], 204);
}
}
Os métodos criados aqui irão recuperar, atualizar e excluir os registros de um CEO do banco de dados.
Agora podemos executar o comando de teste pela última vez usando composer test
e você verá a seguinte saída:
> vendor/bin/phpunit
PHPUnit 8.5.4 by Sebastian Bergmann and contributors.
.......... 10 / 10 (100%)
Time: 915 ms, Memory: 32.00 MB
OK (10 tests, 35 assertions)
Conclusão
Neste tutorial, pudemos usar uma abordagem de desenvolvimento orientado a testes para escrever alguns testes para os endpoints em nosso projeto de API Laravel. Você apreciará ainda mais o desenvolvimento orientado a testes quando descobrir que, uma vez adicionado mais recursos, as novas implementações não interrompem de forma alguma o funcionamento de sua base de código existente.
A base de código completa do tutorial pode ser encontrada . Você pode explorar, adicionar mais testes e escrever o código que permitirá que seu teste seja aprovado de acordo.
Olususi Oluyemi é um entusiasta de tecnologia, fanático por programação e um viciado em desenvolvimento web que adora adotar novas tecnologias.
- Twitter:
- GitHub:
- Site:
Donate to Site
Renato
Developer