web-dev-qa-db-fra.com

Comment puis-je tester un service dans symfony2?

Étant donné que je travaille avec des services, cette question risque d’être un problème d’injection de dépendance dans Symfony. Actuellement, j'essaye de tester une fonctionnalité simple de mon service via le test phpunit et je reçois toujours l'erreur suivante:

PHP Catchable fatal error:  Argument 1 passed to Caremonk\MainSiteBundle\Tests\Services\GeoTest::__construct() must be an instance of Caremonk\MainSiteBundle\Tests\Services\Geo, none given, called in /usr/share/nginx/html/caremonk/vendor/phpunit/phpunit/PHPUnit/Framework/TestSuite.php on line 473 and defined in /usr/share/nginx/html/caremonk/src/Caremonk/MainSiteBundle/Tests/Services/GeoTest.php on line 14

D'après l'erreur, il est évident que j'essaie de créer une instance de mon service et que l'argument correct n'est pas transmis. Par conséquent, mon fichier services.yml est ci-dessous:

#src/Caremonk/MainSiteBundle/Resources/config/services.yml
parameters:
    caremonk_main_site.geo.class: Caremonk\MainSiteBundle\Services\Geo
    caremonk_main_site.geo_test.class: Caremonk\MainSiteBundle\Tests\Services\GeoTest

services:
    geo:
        class: %caremonk_main_site.geo.class%
        arguments: []

    geo_test:
        class: %caremonk_main_site.geo_test.class%
        arguments: ["@geo"]

Ci-dessous, mon service que j'ai construit:

<?php
//src/Caremonk/MainSiteBundle/Services/Geo.php
namespace Caremonk\MainSiteBundle\Services;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class Geo extends Controller
{

    public $pi80;
    public $latRad;
    public $lngRad;

    public function __construct()
    {
        $this->pi80 = M_PI / 180;
    }

    // Takes longitude and latitude and converts them into their respective radians
    // We also set our class properties to these values
    public function setCoordinates($lat,$lng)
    {
        $this->latRad = $lat * $this->pi80;
        $this->lngRad = $lng * $this->pi80;
    }

    public function distance($lat2, $lng2, $miles = true)
    {
        $lat1 = $this->latRad;
        $lng1 = $this->lngRad;
        $lat2 *= $pi80;
        $lng2 *= $pi80;

        $r = 6372.797; // mean radius of Earth in km
        $dlat = ($lat2 - $lat1)/2;
        $dlng = ($lng2 - $lng1)/2;
        $a = sin($dlat) * sin($dlat) + cos($lat1) * cos($lat2) * sin($dlng) * sin($dlng);
        $c = 2 * atan2(sqrt($a), sqrt(1 - $a));
        $km = $r * $c;

        return ($miles ? ($km * 0.621371192) : $km);
    }

    // This function returns the minimum latitude in radians
    public function min_lat($lat,$lng,$dis)
    {
         $dis /= .62137119;
         $ratio = $dis/6372.797;
         return asin(sin($lat)*cos($ratio) + cos($lat)*sin($ratio)*cos(M_PI));
    }

    // This function returns the max latitude in radians
    public function max_lat($lat,$lng,$dis)
    {
         $dis /= .62137119;
         $ratio = $dis/6372.797;
         return asin(sin($lat)*cos($ratio) + cos($lat)*sin($ratio)*cos(0));
    }

    // This function returns max longitude in radians
    public function max_lon($lat,$lng,$dis)
    {
         $dis /= .62137119;
         $ratio = $dis/6372.797;
         return $lng + atan2(sin(M_PI/2)*sin($ratio)*cos($lat),cos($ratio)-sin($lat)*sin($lat));
    }

    // This function returns min longitude in radians
    public function min_lon($lat,$lng,$dis)
    {
         $dis /= .62137119;
         $ratio = $dis/6372.797;
         return $lng + atan2(sin(M_PI*1.5)*sin($ratio)*cos($lat),cos($ratio)-sin($lat)*sin($lat));
    }
}

Mon fichier de test est affiché ici:

<?php
//src/Caremonk/MainSiteBundle/Tests/Services/GeoTest.php

namespace Caremonk\MainSiteBundle\Tests\Services;

use Caremonk\MainSiteBundle\Services;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class GeoTest extends WebTestCase
{
    public $geo; 

    public function __construct(Geo $geo)
    {
        $this->geo = $geo;
    }

    public function testSetCoordinates()
    {
        $this->geo->setCoordinates(4,5);
        //print $geoService->distance(6,5);
    }
}

Enfin, mes services sont enregistrés ci-dessous dans le fichier app/config.yml:

imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    - { resource: "@CaremonkMainSiteBundle/Resources/config/services.yml" }
# Other config.yml stuff

Je ne comprends pas très bien la dépendance et j’espère que mon interprétation de celle-ci, illustrée dans ce message, est proche de ce que Symfony avait en tête. S'il vous plaît laissez-moi savoir ce que je fais mal afin que je puisse tester mon service.

35
Dr.Knowitall

Dans votre scénario de test, vous n'obtiendrez rien dans votre constructeur. Vous pouvez configurer l'objet que vous souhaitez tester dans une méthode setupBeforeClass ou setup, par exemple.

public static function setUpBeforeClass()
{
     //start the symfony kernel
     $kernel = static::createKernel();
     $kernel->boot();

     //get the DI container
     self::$container = $kernel->getContainer();

     //now we can instantiate our service (if you want a fresh one for
     //each test method, do this in setUp() instead
     self::$geo = self::$container->get('geo');
}

(Notez également que vous n'avez pas besoin de configurer vos classes de test en tant que services. Vous pouvez donc supprimer votre service geo_test de services.yml.)

Une fois que vous avez fait cela, vous devriez pouvoir exécuter votre scénario de test avec quelque chose comme:

cd /root/of/project
phpunit -c app src/Caremonk/MainSiteBundle/Tests/Services/GeoTest.php
48
Paul Dixon

Si vous n'allez pas tester les contrôleurs (avec le client), votre classe de test doit simplement étendre KernelTestCase (Symfony\Bundle\FrameworkBundle\Test). Pour amorcer le noyau et obtenir des services, vous pouvez le faire comme ceci dans votre configuration de test:

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class MyTest extends KernelTestCase
{
  public function setUp()
  {
    $kernel = self::bootKernel();
    $this->geo = $kernel->getContainer()->get('geo');
  }
  ...
}

Plus d'infos: http://symfony.com/doc/current/testing/doctrine.html

Remarques: 

  1. Pour avoir un nouveau noyau et des services pour chaque test, je recommande d'utiliser la méthode setUp au lieu de setUpBeforeClass (comme dans la réponse acceptée). 
  2. Le noyau est toujours accessible via static::$kernel (après le démarrage).
  3. Fonctionne également dans Symfony 3 & 4.
27
Rob

Si vous ne souhaitez pas effectuer de tests dans une application mais dans un package autonome, je vous suggère de jeter un coup d'œil à la réponse très détaillée de cette question .

0
Gnucki