2016-11-05 4 views
2

Je le test suivant (ce qui est probablement plus d'un test fonctionnel que l'intégration, mais ...):Grails 3 Intégration Spec a étrange comportement transactionnelles

@Integration(applicationClass = Application) 
@Rollback 
class ConventionControllerIntegrationSpec extends Specification { 

    RestBuilder rest = new RestBuilder() 
    String url 

    def setup() { 
    url = "http://localhost:${serverPort}/api/admin/organizations/${Organization.first().id}/conventions" 
    } 

    def cleanup() { 
    } 

    void "test update convention"() { 
    given: 
    Convention convention = Convention.first() 

    when: 
    RestResponse response = rest.put("${url}/${convention.id}") { 
     contentType "application/json" 
     json { 
     name = "New Name" 
     } 
    } 

    then: 
    response.status == HttpStatus.OK.value() 
    Convention.findByName("New Name").id == convention.id 
    Convention.findByName("New Name").name == "New Name" 

    } 
} 

Les données sont en cours de chargement par l'intermédiaire BootStrap (qui admittadly pourrait être le problème) mais le problème est quand je suis dans le bloc then; il trouve le Convention par le nouveau nom et les correspondances id, mais lors du test du champ name, il échoue car il a toujours l'ancien nom. En lisant la documentation sur les tests, je pense que le problème réside dans la session dans laquelle les données sont créées. Comme le @Rollback a une session distincte de BootStrap, les données ne sont pas vraiment gélifiées. Par exemple, si je charge les données via le bloc given du test, ces données n'existent pas lorsque mon contrôleur est appelé par le RestBuilder.

Il est tout à fait possible que je ne devrais pas faire ce genre de test de cette façon, donc des suggestions sont appréciées.

Répondre

3

Ceci est définitivement un test fonctionnel - vous effectuez des requêtes HTTP sur votre serveur, ne faites pas d'appels de méthode et faites des affirmations sur les effets de ces appels.

Vous ne pouvez pas obtenir des annulations automatiques avec des tests fonctionnels car les appels sont effectués dans un thread et sont traités dans un autre, que le test s'exécute dans la même JVM que le serveur ou non. Le code dans BootStrap s'exécute une fois avant que tous les tests soient exécutés et validés (soit parce que vous avez effectué les modifications dans une transaction ou via autocommit), puis le code de test client s'exécute dans sa propre session Hibernate et dans une transaction l'infrastructure de test démarre (et sera restaurée à la fin de la méthode de test), et le code côté serveur s'exécute dans sa propre session Hibernate (à cause de OSIV) et selon que vos contrôleurs et service (s) sont transactionnel ou non, peut s'exécuter dans une transaction différente, ou peut simplement être autocommit.

Une chose qui n'est probablement pas un facteur ici mais qui devrait toujours être pris en compte avec les tests de persistance Hibernate est la mise en cache de session. La session Hibernate est le cache de premier niveau, et un finder dynamique comme findByName va probablement déclencher un flush, ce que vous voulez, mais devrait être explicite dans les tests. Mais il n'effacera aucun élément mis en cache et vous risquez de recevoir des faux positifs avec un code comme celui-ci, car il est possible que vous ne chargiez pas une nouvelle instance - Hibernate pourrait renvoyer une instance mise en cache. Ce sera certainement lors de l'appel get(). J'ajoute toujours une méthode flushAndClear() aux classes de base d'intégration et fonctionnelle et j'appelle un appel après le put appel dans le bloc when pour être sûr que tout est vidé d'Hibernate à la base de données (non validée, juste vidé) et efface le session pour forcer le rechargement réel. Choisissez simplement une classe de domaine aléatoire et utilisez withSession, par ex.

protected void flushAndClear() { 
    Foo.withSession { session -> 
     session.flush() 
     session.clear() 
    } 
} 

Depuis le put se produit dans un thread/séance/tx et les trouveurs courir dans leur propre, cela ne devrait pas avoir un effet, mais devrait être le modèle utilisé en général.

+0

Merci Burt. Cela aide à clarifier certaines choses. Ce qui me déroutait était dans Grails 2 il y avait une ligne assez claire entre Intégration et Fonctionnelle (puisque les tests fonctionnels n'étaient pas des citoyens de 1ère classe à l'époque) et dans Grails 3, créer un test fonctionnel utilise '@ Integration' et' @Rollback 'dans le modèle pour GebSpec. – Gregg

+2

Les tests fonctionnels sont pour la plupart des citoyens de 1ère classe, mais c'est au plugin de créer le dossier test/fonctionnel.Il est définitivement déroutant que dans Grails 3, les tests d'intégration et les tests fonctionnels partagent le même dossier, et que le serveur web soit démarré pour les tests d'intégration, ce qui brouille encore les différences. –

+0

Wow, ça explique pourquoi je n'ai pas réussi à faire des rollbacks ces derniers jours. Je pensais que quelque chose se passait dans ce sens. Merci beaucoup d'avoir clarifié cela. Existe-t-il des bonnes pratiques pour l'installation et la restauration des données de test fonctionnelles (API)? Je suis en train de configurer mes données via BootStrap, tout comme Gregg, et d'annuler les changements dans les blocs de nettoyage via les appels API une fois le test réussi. –