diff --git a/grails-views-gson/src/main/groovy/grails/plugin/json/view/test/JsonViewTest.groovy b/grails-views-gson/src/main/groovy/grails/plugin/json/view/test/JsonViewTest.groovy index 2069460c132..7daad95ceca 100644 --- a/grails-views-gson/src/main/groovy/grails/plugin/json/view/test/JsonViewTest.groovy +++ b/grails-views-gson/src/main/groovy/grails/plugin/json/view/test/JsonViewTest.groovy @@ -21,6 +21,7 @@ package grails.plugin.json.view.test import groovy.json.JsonSlurper import groovy.text.Template +import groovy.transform.CompileDynamic import groovy.transform.CompileStatic import org.springframework.beans.factory.annotation.Autowired @@ -41,6 +42,7 @@ import grails.web.mapping.LinkGenerator import grails.web.mime.MimeUtility import org.grails.datastore.mapping.keyvalue.mapping.config.KeyValueMappingContext import org.grails.datastore.mapping.model.MappingContext +import org.grails.validation.ConstraintEvalUtils /** * A trait that test classes can implement to add support for easily testing JSON views @@ -104,6 +106,43 @@ trait JsonViewTest { return templateEngine }() + /** + * Resets the {@link ConstraintEvalUtils} default-constraints cache after every feature + * method so state from one test cannot leak into the next test that happens to run in the + * same JVM/fork. + * + *
{@link ConstraintEvalUtils} memoizes the default GORM constraints map in a single + * JVM-wide static field keyed by the identity of the last {@code Config} seen. Since each + * spec implementing this trait typically builds its own {@code GrailsApplication}/{@code + * Config}, a stale entry left behind by one spec can otherwise be picked up by another. + * This is cheap to recompute, so it is safe to clear after every feature.
+ */ + void cleanup() { + ConstraintEvalUtils.clearDefaultConstraints() + } + + /** + * Tears down any {@code GrailsApplication} cached by {@code org.grails.testing.GrailsUnitTest} + * once the whole spec has finished, so a later spec running in the same JVM/fork always starts + * from a clean application context. + * + *This is deliberately a {@code cleanupSpec()} (once per spec class), not a per-feature + * {@code cleanup()}: {@code GrailsUnitTest} intentionally builds its {@code GrailsApplication} + * lazily and reuses it across every feature of a spec (it is expensive to build and some + * traits, e.g. {@code DataTest}, register beans into it once per spec). Tearing it down after + * every feature would rebuild it before the next feature could see those spec-scoped beans.
+ * + *{@code GrailsUnitTest} lives in {@code grails-testing-support-core}, a test-only + * dependency this trait cannot reference directly since it ships as part of the main + * {@code grails-views-gson} artifact, so the call is made dynamically only when present.
+ */ + @CompileDynamic + void cleanupSpec() { + if (metaClass.respondsTo(this, 'cleanupGrailsApplication')) { + cleanupGrailsApplication() + } + } + /** * Render a template for the given source * diff --git a/grails-views-gson/src/test/groovy/grails/plugin/json/view/ExpandSpec.groovy b/grails-views-gson/src/test/groovy/grails/plugin/json/view/ExpandSpec.groovy index 7dfe5befdf5..132baf0f6aa 100644 --- a/grails-views-gson/src/test/groovy/grails/plugin/json/view/ExpandSpec.groovy +++ b/grails-views-gson/src/test/groovy/grails/plugin/json/view/ExpandSpec.groovy @@ -19,6 +19,7 @@ package grails.plugin.json.view import tools.jackson.databind.json.JsonMapper +import grails.persistence.Entity import grails.plugin.json.view.test.JsonRenderResult import grails.plugin.json.view.test.JsonViewTest import org.grails.datastore.mapping.core.Session @@ -32,23 +33,23 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest { JsonMapper objectMapper = JsonMapper.builder().build() void setup() { - mappingContext.addPersistentEntities(Team, Player) + mappingContext.addPersistentEntities(ExpandTeam, ExpandPlayer) } void 'Test expand parameter allows expansion of child associations'() { given: 'An entity with a proxy association' def mockSession = Mock(Session) mockSession.getMappingContext() >> mappingContext - mockSession.retrieve(Team, 1L) >> new Team(name: 'Manchester United') - def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, Team, 1L) + mockSession.retrieve(ExpandTeam, 1L) >> new ExpandTeam(name: 'Manchester United') + def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, ExpandTeam, 1L) - def player = new Player(name: 'Cantona', team: teamProxy) + def player = new ExpandPlayer(name: 'Cantona', team: teamProxy) def templateText = ''' - import grails.plugin.json.view.* - + import grails.plugin.json.view.ExpandPlayer as Player + @Field Player player - + json g.render(player) ''' @@ -84,15 +85,13 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest { given: 'An entity with a proxy association' def mockSession = Mock(Session) mockSession.getMappingContext() >> mappingContext - mockSession.retrieve(Team, 1L) >> new Team(name: 'Manchester United') - def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, Team, 1L) + mockSession.retrieve(ExpandTeam, 1L) >> new ExpandTeam(name: 'Manchester United') + def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, ExpandTeam, 1L) - def player = new Player(name: 'Cantona', team: teamProxy) + def player = new ExpandPlayer(name: 'Cantona', team: teamProxy) def templateText = ''' - import grails.plugin.json.view.* - @Field Map map - + json g.render(map) ''' @@ -119,13 +118,13 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest { given: 'An entity with a proxy association' def mockSession = Mock(Session) mockSession.getMappingContext() >> mappingContext - mockSession.retrieve(Team, 1L) >> new Team(name: 'Manchester United') - def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, Team, 1L) + mockSession.retrieve(ExpandTeam, 1L) >> new ExpandTeam(name: 'Manchester United') + def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, ExpandTeam, 1L) - def player = new Player(name: 'Cantona', team: teamProxy) + def player = new ExpandPlayer(name: 'Cantona', team: teamProxy) def templateText = ''' - import grails.plugin.json.view.* + import grails.plugin.json.view.ExpandPlayer as Player model { Player player } @@ -140,7 +139,7 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest { { "_links": { "self": { - "href": "http://localhost:8080/player", + "href": "http://localhost:8080/expandPlayer", "hreflang": "en", "type": "application/hal+json" } @@ -161,7 +160,7 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest { "team": { "_links": { "self": { - "href": "http://localhost:8080/team/1", + "href": "http://localhost:8080/expandTeam/1", "hreflang": "en", "type": "application/hal+json" } @@ -171,7 +170,7 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest { }, "_links": { "self": { - "href": "http://localhost:8080/player", + "href": "http://localhost:8080/expandPlayer", "hreflang": "en", "type": "application/hal+json" } @@ -185,18 +184,18 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest { given: 'An entity with a proxy association' def mockSession = Mock(Session) mockSession.getMappingContext() >> mappingContext - mockSession.retrieve(Team, 9L) >> new Team(name: 'Manchester United') - def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, Team, 9L) - def player = new Player(name: 'Cantona', team: teamProxy) + mockSession.retrieve(ExpandTeam, 9L) >> new ExpandTeam(name: 'Manchester United') + def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, ExpandTeam, 9L) + def player = new ExpandPlayer(name: 'Cantona', team: teamProxy) player.id = 3 when: 'The domain is rendered with expand parameters' JsonRenderResult result = render(''' - import grails.plugin.json.view.* + import grails.plugin.json.view.ExpandPlayer as Player model { Player player } - + json jsonapi.render(player, [expand: 'team']) ''', [player: player]) @@ -204,7 +203,7 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest { objectMapper.readTree(result.jsonText) == objectMapper.readTree(''' { "data": { - "type": "player", + "type": "expandPlayer", "id": "3", "attributes": { "name": "Cantona" @@ -212,21 +211,21 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest { "relationships": { "team": { "links": { - "self": "/team/9" + "self": "/expandTeam/9" }, "data": { - "type": "team", + "type": "expandTeam", "id": "9" } } } }, "links": { - "self": "/player/3" + "self": "/expandPlayer/3" }, "included": [ { - "type":"team", + "type":"expandTeam", "id": "9", "attributes": { "titles": null, @@ -241,7 +240,7 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest { } }, "links": { - "self": "/team/9" + "self": "/expandTeam/9" } } ] @@ -249,3 +248,25 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest { ''') } } + +@Entity +class ExpandTeam { + String name + ExpandPlayer captain + List players + List