3 votes

Comment tester en unité que l'objet a été passé avec un état correct ?

Disons qu'il existe une telle fonction :

function a() 
{
    $entity = $this->getEntity();

    $entity->setSomePrivateVar();

    $service = $this->getService();

    $service->doSomething($entity);

}

Je veux tester que

 $service->doSomething($entity);

est appelé avec la bonne $entité.

L'entité $ appelle setSomePrivateVar()

Dans un code d'application réel, j'ai fait quelque chose comme ça :

Obtenez une copie de l'entité et testez que setSomePrivateVar est appelé.

Obtenez un objet fantaisie de $service et testez que doSomething() est appelé avec le paramètre $entity.

Ça a l'air bien.

Mais le problème est que si je remanie le code et appelle d'abord doSomething() sur le service et ensuite setSomePrivateVar() sur $entity, le test passe toujours.

Mais la fonction est maintenant fausse, car doSomething dépend du champ privé de $entity qui est défini par setSomePrivateVar().

Par exemple, je le remanierais comme suit :

function a() 
{
    $entity = $this->getEntity();

    $service = $this->getService();

    $service->doSomething($entity);

    // this line moved
    $entity->setSomePrivateVar();

}

Il semble donc que PhpUnit ne vérifie pas les champs privés de $entity. Si c'était par exemple un tableau, alors la fonction with() verrait que le tableau passé n'est pas le même que celui attendu.

Alors comment puis-je tester que doSomething() obtient $entity dans un état correct (que setSomePrivateVar() a été appelé sur l'entité avant de la passer à doSomething()) ?

Peut-être que ça a quelque chose à voir avec le fait que $entity soit moqué.

Mise à jour avec un exemple concret

public function setNotifyUsers(AnnualConsolidation $consolidation, $status)
{
    $consolidation->setNotifyUsers($status);    // if move this method after the flush(), tesst does not fail

    $this->entityManager->persist($consolidation);
    $this->entityManager->flush();
}

public function testNotifyUsers()
{
    $consolidation = $this->getMockBuilder(AnnualConsolidation::class)
        ->setMethods(['setNotifyUsers'])
        ->getMock();

    $consolidation
        ->expects($this->once())
        ->method('setNotifyUsers')
    ;

    $this->entityManager
        ->expects($this->at(0))
        ->method('persist')
        ->with($consolidation)
    ;

    $this->entityManager
        ->expects($this->at(1))
        ->method('flush')
    ;

    /** @var AnnualConsolidation $consolidation */
    $this->consolidationsService->setNotifyUsers($consolidation, true);
}

Nous discutions de la pertinence de tester la méthode setNotifyUsers de cette manière. J'essayais de tester sans toucher à la base de données. L'un d'entre eux pense qu'il pourrait être nécessaire de tester avec la base de données, car si l'on remanie la méthode sans changer la logique, le test pourrait être nécessaire pour la remanier. D'un autre côté, il est peu probable que cette méthode soit remaniée à ce point.

Mais il existe peut-être aussi un moyen de simplement tester que flush() est appelé après persist() sans en informer les index, car dans d'autres exemples, les index pourraient devoir être mis à jour après l'ajout d'un appel avant persist et cela pourrait représenter trop de travail pour que les tests fonctionnent.

Mais pour ce sujet - je veux d'abord savoir comment faire échouer le test - si je déplace setNotifyUsers après le flush(). Le test n'échoue pas. Alors que si nous faisions un test avec la base de données de frappe - nous verrions que le statut de $consolidation n'est pas mis à jour.

Un type m'a dit de vérifier, d'affirmer ce qui est passé à la méthode persist. Je n'ai pas encore essayé, mais je ne suis pas sûr que cela soit possible sur une $consolidation fantaisie. Est-ce que le mocked $consolidation a un état comme le vrai $consolidation aurait ?

0voto

melvin Points 975

Comme vous le dites dans votre question

Un type m'a dit de vérifier, d'affirmer ce qui est passé à la méthode persist.

Ce serait la meilleure solution, mais votre code rend la chose plutôt difficile et je pense que vous devriez le remanier un peu pour le rendre testable.

Tout d'abord, votre méthode s'appelle "setNotifyUsers" mais elle effectue en fait 2 actions : elle appelle le setNotifyUsers sur l'objet de consolidation et elle sauvegarde/persiste ces données. À mon avis, ce sont deux actions différentes qui devraient appartenir à deux méthodes différentes. Cela pourrait aider votre test si vous l'écriviez comme suit :

public function setNotifyUsers(AnnualConsolidation $consolidation, $status) {
  $consolidation->setNotifyUsers($status);    
}

public function persistConsolidation(AnnualConsolidation $consolidation) {
  $this->entityManager->persist($consolidation);
  $this->entityManager->flush();
}

Vous pouvez tester les fonctions setNotifyUser et persistConsolidation séparément et écrire un test fonctionnel pour la partie qui appelle ces fonctions (les méthodes qui utilisent le consolidationsService). vous pouvez ensuite utiliser la fonctionnalité at() pour vérifier si ces fonctions sont appelées dans le bon ordre.

Mais : Deuxièmement, vous donnez à la fois l'état comme la consolidation à cette fonction avec comme seule raison d'ajouter les deux ensemble. Je ne pense pas que quelque chose comme ça appartient au service mais plutôt à la méthode qui appelle ce service. Déplacer cette fonctionnalité vous donnera à nouveau des problèmes car vous ne pouvez pas tester l'ordre dans lequel ils sont appelés.

Mais vous n'avez pas besoin d'utiliser le mockBuilder pour faire un double test. Au lieu d'utiliser $this->getMockBuilder, vous pouvez aussi créer un FakeConsolidation qui contiendra réellement les données pour vous

Ensuite, vous aurez également besoin d'un simulateur pour AnnualConsolidation, car vous voulez pouvoir vérifier si la valeur a été correctement définie.

class FakeConsolidation extends AnnualConsolidation {

  protected $id; 
  proteced $status;

  public function getId() {
    return $this->id;
  }

  public function setId($id) {
     $this->id = $id;
  }

  public function setNotifyUsers($status) {
    $this->status = $status;
  }

  public function shouldNotifyUsers() {
    $this->status
  } 
}

Maintenant, comme vous allez donner un objet au persist qui a un état, nous pouvons vérifier cet état dans la partie "with".

Bien sûr, je ne sais pas exactement comment votre code est structuré, j'ai donc fait quelques suppositions ; il suffit d'adapter là où c'est nécessaire et d'utiliser les interfaces dont vous disposez.

Ainsi, vous pourriez même tester le code tel que vous l'avez présenté dans cette question :

class SomethingTest extends PHPUnit_Framework_TestCase {
  private $consolidationsService;
  private $entityManager;

  /**
   * {@inheritdoc}
   */
  public function setUp() {
    $this->entityManager = $this->getMockBuilder(EntityManager::class)->getMock();
    $this->consolidationsService = new ConsolidationsService($this->entityManager);
  }

  public function testNotifyUsers() {
    $consolidation = new FakeConsolidation();
    $consolidation->setId(1);
    $this->entityManager
      ->expects($this->at(0))
      ->method('persist')
      ->with($this->callback(
          function($savedConsolidation) {
            return $savedConsolidation->shouldNotifyUsers() === true;
          }
      ));

    $this->entityManager
      ->expects($this->at(1))
      ->method('flush');

    /** @var AnnualConsolidation $consolidation */
    $this->consolidationsService->setNotifyUsers($consolidation, TRUE);
  }

}

Maintenant, lorsque vous déplacez le setNotifyUsers sous le persistent

with($this->callback(
              function($savedConsolidation) {
                return $savedConsolidation->shouldNotifyUsers() === true;
              }
          )); 

Votre test échouera car l'état n'est pas encore défini.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X