2016-11-05 3 views
2

Ich habe folgende Test (die wahrscheinlich eher ein Funktionstest als Integration ist aber ...):Grails 3 Integration Spec hat merkwürdige Transactional Behavior

@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" 

    } 
} 

Die Daten über BootStrap wird geladen (die admittadly könnte das Problem sein), aber das Problem ist, wenn ich im then Block bin; Es findet die Convention durch den neuen Namen und die id Übereinstimmungen, aber beim Testen der name Feld ist es fehlgeschlagen, weil es immer noch den alten Namen hat.

Beim Lesen der Dokumentation zum Testen denke ich, das Problem liegt in welcher Sitzung die Daten erstellt werden. Da die @Rollback eine Sitzung hat, die von BootStrap getrennt ist, sind die Daten nicht wirklich Gelieren. Zum Beispiel, wenn ich die Daten über den given Block des Tests lade, dann sind diese Daten nicht vorhanden, wenn mein Controller vom RestBuilder aufgerufen wird.

Es ist durchaus möglich, dass ich diese Art von Test nicht auf diese Weise machen sollte, daher werden Vorschläge geschätzt.

Antwort

3

Dies ist definitiv ein Funktionstest - Sie stellen HTTP-Anfragen an Ihren Server, machen keine Methodenaufrufe und machen dann Aussagen über die Auswirkungen dieser Anrufe.

Sie können keine automatischen Rollbacks mit Funktionstests erhalten, da die Aufrufe in einem Thread und in einem anderen ausgeführt werden, unabhängig davon, ob der Test in derselben JVM wie der Server ausgeführt wird oder nicht. Der Code in BootStrap wird einmal ausgeführt, bevor alle Tests ausgeführt werden und festgeschrieben werden (entweder, weil Sie die Änderungen in einer Transaktion oder über Autocommit vorgenommen haben), und dann wird der Client-Testcode in einer eigenen neuen Hibernate-Sitzung und in einer Transaktion ausgeführt Die Testinfrastruktur startet (und wird am Ende der Testmethode zurückgesetzt), und der serverseitige Code wird in seiner eigenen Hibernate-Sitzung (aufgrund von OSIV) und abhängig davon ausgeführt, ob Controller und Dienst (e) vorhanden sind Transaktional oder nicht, kann in einer anderen Transaktion ausgeführt werden, oder Autocommit einfach.

Eine Sache, die hier wahrscheinlich kein Faktor ist, aber bei Hibernate-Persistenztests immer berücksichtigt werden sollte, ist das Session-Caching. Die Hibernate-Sitzung ist der Cache der ersten Ebene, und ein dynamischer Finder wie findByName wird wahrscheinlich einen Flush auslösen, den Sie wollen, aber sollte in Tests explizit sein. Es werden jedoch keine zwischengespeicherten Elemente gelöscht, und Sie riskieren False Positives mit Code wie diesem, da Sie möglicherweise keine neue Instanz laden - Hibernate kann eine zwischengespeicherte Instanz zurückgeben. Es wird definitiv beim Aufruf get(). Ich füge immer eine flushAndClear() Methode zur Integration und funktionale Basisklassen und ich würde einen Anruf nach der put Anruf in der when Block um sicher zu sein, dass alles aus Hibernate in die Datenbank geleert (nicht festgeschrieben, nur geleert) und löschen Sie die Sitzung, um echtes Nachladen zu erzwingen. Wählen Sie einfach eine zufällige Domain-Klasse und verwenden Sie withSession, z.

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

Da die put in einer in ihrem eigenen Thread laufen/session/tx und den Findern geschieht, sollte dies keine Auswirkungen haben, aber sollte das Muster im allgemeinen verwendet werden.

+0

Danke Burt. Das hilft, einige Dinge zu klären. Was mich verwirrte, war, dass es in Grails 2 eine ziemlich klare Grenze zwischen Integration und Funktional gab (da funktionale Tests zu der Zeit keine Bürger der ersten Klasse waren) und in Grails 3, die Erstellung eines Funktionstests mit '@ Integration' und' @Rollback 'in der Vorlage für die GebSpec. – Gregg

+2

Funktionstests sind meist Bürger der ersten Klasse, aber es ist Sache des Plugins, den Test-/Funktionsordner zu erstellen.Es ist auf den ersten Blick verwirrend, dass in Grails 3 die Integration und die Funktionstests den gleichen Ordner teilen und dass der Webserver für Integrationstests gestartet wird, was die Unterschiede weiter verwischt. –

+0

Wow, das erklärt, warum ich in den letzten Tagen keine Rollbacks bekommen konnte. Ich dachte mir, dass etwas in dieser Richtung passierte. Vielen Dank für die Klärung. Gibt es Best Practices für die Einrichtung und das Zurücksetzen funktionaler (API) Testdaten? Ich bereite gerade meine Daten über BootStrap, genau wie Gregg, vor und mache Änderungen in Aufräumblöcken über API-Aufrufe rückgängig, sobald der Test bestanden hat. –