I am writting this gist for two reasons:
- I promised I will write it. Hey Dazz 👋
- It seems that more than 1 (one) person has found this an interesting approach
A small preface: This was applied in an API project built with Symfony (API Platform). That is, there was no front-end, or HTML responses. This API project
supports only json
or json-ld
. Our tests used the Symfony test-pack (PHPUnit and some Symfony helper classes).
As a team, after some cycles (sprints) of trying things out, we figured out that certain tests followed the same principles. Most of you have already read and even implmeneted them, but as a development team we saw that principles can be interpreted and this would cause confusion, or debates, all in good spirit, on "Where do I put this test case?".
In an attempt to have each member answer that question without the help of the team while developing, we came up with the following categories and the requirements they had to accept tests in them:
- Tests that cover classes without dependencies, a.k.a
__construct()
. - An exception can be made for classes who have at most a
1 level
deep constructor whose dependencies are classes without dependencies. - All unit tests must extend
TestCase
from PHPUnit.
examples:
class Place
{
private string $where;
public function __construct()
{
$this->where = '';
}
}
class Time
{
private string $when = '10:00';
}
class Appointment
{
public function __construct(private Place $pace, private Time $time)
{
}
}
- Tests that cover classes with dependencies from the Symfony service container.
- Tests that cover classes that are Symfony services.
- Tests whose main goal is a part of a functionality.
- All integration tests must extend
KernelTestCase
from Symfony.
examples:
class AppointmentHandler
{
public function __construct(private NoWorkCalendar $noWorkCalendar)
{
}
public function handle(Appointment $appointment): void
{
$regionNoWorkCalendar = $this->noWorkCalendar->getRegionalCalendar($appointment->getCountry());
$result = $regionNoWorkCalendar->canHappen($appointment);
// ...
}
}
- Tests that cover the same API resource or one endpoint.
- Tests that cover multiple payloads but for the same endpoint.
- query parameters alter the endpoint.
- All functional tests must extend
ApiTestCase
from API Platform.- in particular cases (when a clueless client is needed)
WebTestCase
from Symfony is also acceptable.
- in particular cases (when a clueless client is needed)
examples:
POST /appointments
GET /appointments
GET /appointments/{id}
PUT /appointments/{id}
...
- Tests where more than one endpoint is called.
- Tests where multiple query parameters combinations are needed.
- Tests that represent a user-story.
- All acceptance tests must extend
ApiTestCase
from API Platform. - in particular cases (when a clueless client is needed)
WebTestCase
from Symfony is also acceptable.
examples:
search:
GET /appointment/search:
- from
- to
- onlyAfter
- allowWeekends
- page
- itemsPerPage
- ...
a-person-made-a-mistake:
POST /appointments
POST /correction
GET /appointments
DELETE /appointments/{id}
POST /appointments