Maison  >  Article  >  développement back-end  >  Création d'une façade testable dans Laravel

Création d'une façade testable dans Laravel

Barbara Streisand
Barbara Streisandoriginal
2024-09-29 06:08:30260parcourir

Creating a Testable Facade in Laravel

Voici un aide-mémoire sur la façon de rendre votre classe de service simple plus utile en ajoutant une injection de dépendances, une façade et un moyen d'échanger facilement un faux.

Le squelette est simple :

  • La classe de service d'origine
  • Créez un contrat que la classe de service respecte
  • Chez un prestataire de services, enregistrez la classe de service dans le conteneur
  • Créer une façade
  • Créer une fausse implémentation du contrat qui peut être échangée contre des tests

La classe de service d'origine

Voici notre classe de service d'origine avec laquelle nous commençons (désolé de ne pas avoir d'exemple convaincant, mais il n'est pas vraiment nécessaire d'en créer un pour cela).

<?php

namespace App\Foo;

class FooService
{
    public function foo(): string
    {
        return 'bar';
    }

    public function fizz(): string
    {
        return 'buzz';
    }
}

Le contrat

Tout d'abord, nous devons créer un contrat afin de pouvoir nous assurer que notre éventuel faux et notre service original répondent tous deux aux attentes. Ainsi que toute implémentation future.

<?php

namespace App\Foo\Contracts;

interface Foo
{
    public function foo(): string;

    public function fizz(): string;
}

N'oubliez pas de vous assurer que le service le met en œuvre.

<?php

namespace App;

use App\Foo\Contracts\Foo;

class FooService implements Foo
{
   // ...
}

Liaison au conteneur

Ensuite, nous devrions lier la mise en œuvre concrète au contrat chez notre prestataire de services.

<?php

namespace App\Providers;

use App\Foo\Contracts\Foo;
use App\FooService;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->bind(Foo::class, FooService::class);
    }

   // ...
}

La façade

Maintenant, nous pouvons créer notre classe de façade.

<?php

namespace App\Foo\Facades;

use Illuminate\Support\Facades\Facade;

/**
* @method static string foo(): string
* @method static string fizz(): string
*/
class Foo extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return \App\Foo\Contracts\Foo::class;
    }
}

La façade a simplement besoin du nom de la liaison qu'elle extraira du conteneur pour être renvoyée par getFacadeAccessor. Dans notre cas, c'est le nom du contrat auquel notre service est actuellement lié.

Notez que si vous souhaitez le support de l'IDE, vous devrez redéfinir les signatures de méthode dans le bloc doc au-dessus de la classe.

À ce stade, nous pouvons utiliser notre façade.

Usage

<?php

namespace App\Http\Controllers;

use App\Foo\Facades\Foo;

class FooController extends Controller
{
    public function index()
    {
        return response()->json([
            'foo' => Foo::foo(),
        ]);
    }
}

Alternativement, nous pouvons également l'injecter en tant que dépendance.

<?php

namespace App\Http\Controllers;

use App\Foo\Contracts;

class FooController extends Controller
{
   public function __construct(protected Foo $foo) {}

    public function index()
    {
        return response()->json([
            'foo' => $this->foo->foo(),
        ]);
    }
}

Faire semblant de la façade

Laravel propose souvent un moyen astucieux de simuler facilement ses façades, par ex. Événement :: faux (). Nous pouvons le mettre en œuvre nous-mêmes.

Tout ce que nous avons à faire est de créer la fausse mise en œuvre de notre contrat, puis d'ajouter la fausse méthode à notre façade.

<?php

namespace App\Foo;

use App\Foo\Contracts\Foo;

class FakeFooService implements Foo
{
    public function __construct(public Foo $actual) {}

    public function foo(): string
    {
        return 'fake';
    }

    public function fizz(): string
    {
        return 'very fake';
    }
}

Dans notre fausse implémentation, nous créons également une référence publique à la classe concrète « réelle ».

Et voici notre fausse implémentation de façade. Vous pouvez voir que nous utilisons cette référence au réel.

<?php

namespace App\Foo\Facades;

use App\Foo\FakeFooService;
use Illuminate\Support\Facades\Facade;

/**
* @method static string foo(): string
* @method static string fizz(): string
*/
class Foo extends Facade
{
    public static function fake()
    {
        $actual = static::isFake()
            ? static::getFacadeRoot()->actual
            : static::getFacadeRoot();

        tap(new FakeFooService($actual), function ($fake) {
            static::swap($fake);
        });
    }

   // ...
}

Un test de base

Écrivons maintenant un test rapide qui correspond à l'exemple de contrôleur que nous avons créé ci-dessus.

<?php

namespace Tests\Feature;

use App\Foo\Facades\Foo;
use Illuminate\Testing\Fluent\AssertableJson;
use Tests\TestCase;

class FooTest extends TestCase
{
    public function test_foo(): void
    {
        $response = $this->get('/');

        $response->assertJson(fn (AssertableJson $json)
            => $json->where('foo', 'bar'));
    }

    public function test_fake_foo(): void
    {
        Foo::fake();

        $response = $this->get('/');

        $response->assertJson(fn (AssertableJson $json)
            => $json->where('foo', 'fake'));
    }
}

Les tests ne sont pas utiles mais ils montrent à quel point il est facile d'utiliser notre faux. Dans test_fake_foo nous obtenons foo=fake tandis que test_foo renvoie foo=bar.

Aller plus loin dans les tests

Ce qui est amusant avec les contrefaçons, c'est que dans notre implémentation de fausses, nous pouvons ajouter des méthodes supplémentaires pour tester tout ce que nous pourrions trouver utile. Par exemple, nous pourrions activer un compteur dans la méthode foo de notre faux qui s'incrémente à chaque fois que nous appelons foo. Ensuite, nous pourrions ajouter une méthode appelée assertFooCount où nous pouvons affirmer que la méthode a été appelée autant de fois que prévu.

<?php

namespace App\Foo;

use App\Foo\Contracts\Foo;
use Illuminate\Testing\Assert;

class FakeFooService implements Foo
{
    public int $fooCount = 0;

    public function __construct(public Foo $actual) {}

    public function foo(): string
    {
        $this->fooCount++;

        return 'fake';
    }

    public function fizz(): string
    {
        return 'very fake';
    }

    public function assertFooCount(int $count)
    {
        Assert::assertSame($this->fooCount, $count);
    }
}

Comme vous pouvez le voir, nous utilisons IlluminateTestingAssert de Laravel pour faire l'assertion. Alors notre test peut ressembler à ceci.

public function test_incrementor(): void
{
    Foo::fake();

    Foo::foo();
    Foo::foo();
    Foo::foo();

    Foo::assertFooCount(3); // pass!
}

C'est ça !

Tout n'a pas besoin d'une façade, mais lorsque vous créez des outils/packages utilisés en interne, une façade est souvent un modèle solide sur lequel s'appuyer.

Voici le dépôt avec tout le code : https://github.com/ClintWinter/laravel-facade-example

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn