Classes utilitaires
HttpAssert
La classe Tester\HttpAssert fournit des outils pour tester des serveurs HTTP. Elle vous permet d'effectuer
facilement des requêtes HTTP et de vérifier les codes de statut, les en-têtes et le contenu du corps de la réponse à l'aide
d'une interface fluide.
# Requête HTTP de base et vérification de la réponse
$response = Tester\HttpAssert::fetch('https://example.com/api/users');
$response
->expectCode(200)
->expectHeader('Content-Type', contains: 'json')
->expectBody(contains: 'users');
La méthode fetch() crée par défaut une requête GET, mais tous les paramètres peuvent être
personnalisés :
HttpAssert::fetch(
'https://api.example.com/users',
method: 'POST',
headers: [
'Authorization' => 'Bearer token123', # tableau associatif
'Accept: application/json', # ou format chaîne
],
cookies: ['session' => 'abc123'],
follow: false, # ne pas suivre les redirections
body: '{"name": "John"}'
)
->expectCode(201);
Les codes de statut se vérifient à l'aide des méthodes expectCode() et denyCode(). Vous pouvez
passer soit un nombre précis, soit une fonction de validation :
$response
->expectCode(200) # code exact
->expectCode(fn($code) => $code < 400) # validation personnalisée
->denyCode(404) # ne doit pas être 404
->denyCode(fn($code) => $code >= 500); # ne doit pas être une erreur serveur
Pour vérifier les en-têtes, utilisez les méthodes expectHeader() et denyHeader(). Vous pouvez
contrôler qu'un en-tête existe, vérifier sa valeur exacte, ou faire correspondre une partie de son contenu :
$response
->expectHeader('Content-Type') # l'en-tête doit exister
->expectHeader('Content-Type', 'application/json') # valeur exacte
->expectHeader('Content-Type', contains: 'json') # contient le texte
->expectHeader('Server', matches: 'nginx %a%') # correspond au motif
->denyHeader('X-Powered-By') # l'en-tête ne doit pas exister
->denyHeader('X-Debug', contains: 'sensitive') # ne doit pas contenir le texte
->denyHeader('X-Debug', matches: '~debug~i'); # ne doit pas correspondre au motif
La vérification du corps de la réponse fonctionne de la même façon avec les méthodes expectBody() et
denyBody() :
$response
->expectBody('OK') # valeur exacte
->expectBody(contains: '"status": "success"') # contient un fragment JSON
->expectBody(matches: '%A% hello %A%') # correspond au motif
->expectBody(fn($body) => json_decode($body) !== null) # validation personnalisée
->denyBody('Error occurred') # ne doit pas avoir cette valeur exacte
->denyBody(contains: 'error') # ne doit pas contenir le texte
->denyBody(matches: '~exception|fatal~i'); # ne doit pas correspondre au motif
Le paramètre follow détermine la façon dont HttpAssert traite les redirections HTTP :
# Test d'une redirection sans la suivre (par défaut)
HttpAssert::fetch('https://example.com/redirect', follow: false)
->expectCode(301)
->expectHeader('Location', 'https://example.com/new-url');
# Suivi de toutes les redirections jusqu'à la réponse finale
HttpAssert::fetch('https://example.com/redirect', follow: true)
->expectCode(200)
->expectBody(contains: 'final content');
DomQuery
Tester\DomQuery est une classe étendant SimpleXMLElement, qui permet d'interroger facilement du HTML
ou du XML à l'aide de sélecteurs CSS.
# crée un DomQuery à partir d'une chaîne HTML
$dom = Tester\DomQuery::fromHtml('
<article class="post">
<h1>Title</h1>
<div class="content">Text</div>
</article>
');
# teste l'existence d'un élément à l'aide de sélecteurs CSS
Assert::true($dom->has('article.post'));
Assert::true($dom->has('h1'));
# trouve les éléments sous forme de tableau d'objets DomQuery
$headings = $dom->find('h1');
Assert::same('Title', (string) $headings[0]);
# teste si l'élément correspond au sélecteur (depuis la version 2.5.3)
$content = $dom->find('.content')[0];
Assert::true($content->matches('div'));
Assert::false($content->matches('p'));
# trouve l'ancêtre le plus proche correspondant au sélecteur (depuis la 2.5.5)
$article = $content->closest('.post');
Assert::true($article->matches('article'));
Pour les documents XML, utilisez la méthode fromXml() :
$dom = Tester\DomQuery::fromXml('<catalog><item>First</item></catalog>');
Assert::true($dom->has('item'));
FileMock
Tester\FileMock émule des fichiers en mémoire et facilite le test du code qui utilise des fonctions comme
fopen(), file_get_contents(), parse_ini_file() et consorts. Exemple d'utilisation :
# Classe testée
class Logger
{
public function __construct(
private string $logFile,
) {
}
public function log(string $message): void
{
file_put_contents($this->logFile, $message . "\n", FILE_APPEND);
}
}
# Nouveau fichier vide
$file = Tester\FileMock::create('');
$logger = new Logger($file);
$logger->log('Login');
$logger->log('Logout');
# Teste le contenu créé
Assert::same("Login\nLogout\n", file_get_contents($file));
Le second paramètre facultatif $extension définit l'extension du fichier dans l'URL générée, ce qui est
pratique lorsque le code testé prend ses décisions d'après elle :
$file = Tester\FileMock::create('{"key": "value"}', 'json');
Assert::with()
Ce n'est pas une assertion, mais une aide pour tester les méthodes et propriétés privées des objets.
class Entity
{
private $enabled;
// ...
}
$ent = new Entity;
Assert::with($ent, function () {
Assert::true($this->enabled); // accès à la propriété privée $ent->enabled
});
Helpers::purge()
La méthode purge() crée le répertoire indiqué et, s'il existe déjà, supprime tout son contenu. Elle est
utile pour créer un répertoire temporaire. Par exemple dans tests/bootstrap.php :
@mkdir(__DIR__ . '/tmp'); # @ - le répertoire peut déjà exister
define('TempDir', __DIR__ . '/tmp/' . getmypid());
Tester\Helpers::purge(TempDir);
Environment::lock()
Les tests s'exécutent en parallèle. Il arrive cependant que nous ayons besoin que leur exécution ne se chevauche pas. C'est
typiquement le cas des tests de base de données, qui exigent de préparer le contenu de la base et de garantir qu'aucun autre
test n'y touche pendant leur exécution. Dans ces tests, nous utilisons Tester\Environment::lock($name, $dir) :
Tester\Environment::lock('database', __DIR__ . '/tmp');
Le premier paramètre est le nom du verrou, le second le chemin du répertoire où le stocker. Le test qui obtient le verrou en premier poursuit, les autres tests doivent attendre qu'il se termine.
Environment::bypassFinals()
Les classes ou méthodes marquées final sont difficiles à tester. Appeler
Tester\Environment::bypassFinals() au début d'un test fait omettre les mots-clés final lors du
chargement du code.
require __DIR__ . '/bootstrap.php';
Tester\Environment::bypassFinals();
class MyClass extends NormallyFinalClass # <-- NormallyFinalClass n'est plus final
{
// ...
}
Environment::setup()
- améliore la lisibilité des dumps d'erreurs (colorisation comprise) ; sinon, c'est la pile d'appels PHP par défaut qui est affichée
- active le contrôle que des assertions ont bien été appelées dans le test ; sinon, les tests sans assertions (oubliées, par exemple) passent aussi
- démarre automatiquement la collecte d'informations sur le code exécuté (quand
--coverageest utilisé) (décrit plus loin) - affiche le statut OK ou FAILURE à la fin du script
Environment::setupFunctions()
Crée les fonctions globales test(), testException(), testNoError(),
setUp() et tearDown(), dans lesquelles vous pouvez structurer vos tests.
test('description du test', function () {
Assert::same(123, foo());
Assert::false(bar());
// ...
});
Environment::VariableRunner
Permet de déterminer si le test a été lancé directement ou via Tester.
if (getenv(Tester\Environment::VariableRunner)) {
# lancé par Tester
} else {
# lancé autrement
}
Environment::VariableThread
Tester exécute les tests en parallèle dans le nombre de threads indiqué. Si le numéro du thread nous intéresse, nous le trouvons dans la variable d'environnement :
echo "Exécution dans le thread numéro " . getenv(Tester\Environment::VariableThread);