Skip to content
This repository was archived by the owner on Oct 11, 2025. It is now read-only.

Commit 8f1ee46

Browse files
authored
Merge pull request #564 from jdaugherty/7.0.x
#13552 Integrate grails-web-testing-support from grails/grails-testing-support into grails-gsp
2 parents 54e4a7e + 7292f57 commit 8f1ee46

20 files changed

+1623
-1
lines changed

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ commonsTextVersion=1.13.0
1010
elApiVersion=6.0.1
1111
jspApiVersion=4.0.0
1212
sitemeshVersion=3.2.2
13+
javassistVersion=3.30.2-GA
1314

1415
# This prevents the Grails Gradle Plugin from unnecessarily excluding slf4j-simple in the generated POMs
1516
# https://github.com/grails/grails-gradle-plugin/issues/222

grails-plugin-gsp/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ dependencies {
161161
testImplementation 'org.spockframework:spock-core'
162162
testImplementation 'org.grails:grails-gorm-testing-support'
163163
testImplementation 'org.grails:grails-testing-support'
164-
testImplementation 'org.grails:grails-web-testing-support'
164+
testImplementation project(':grails-web-testing-support')
165165

166166
testRuntimeOnly 'org.glassfish.web:jakarta.servlet.jsp.jstl'
167167
testRuntimeOnly 'org.grails:grails-plugin-url-mappings'
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
version = projectVersion
2+
group = 'org.grails'
3+
4+
apply plugin: 'groovy'
5+
apply plugin: "java-library"
6+
7+
ext {
8+
pomTitle = 'Grails Web Testing Support'
9+
pomDescription = 'Support for writing concise expressive tests for Grails Web Artifacts'
10+
pomDevelopers = [
11+
[id: 'jeffscottbrown', name: 'Jeff Brown'],
12+
[id: 'jameskleeh', name: 'James Kleeh']
13+
]
14+
}
15+
16+
// Note: documentation for this project is published to grails-doc as part of the other testing documentation
17+
18+
dependencies {
19+
implementation platform("org.grails:grails-bom:$grailsVersion")
20+
21+
api project(':grails-plugin-gsp')
22+
api 'org.grails:grails-plugin-rest'
23+
api 'org.grails:grails-plugin-interceptors'
24+
api 'jakarta.servlet:jakarta.servlet-api'
25+
26+
api 'org.grails:grails-testing-support'
27+
api "org.javassist:javassist:${javassistVersion}"
28+
}
29+
30+
apply from: rootProject.file('gradle/java-config.gradle')
31+
apply from: rootProject.file('gradle/test-config.gradle')
32+
apply from: rootProject.file('gradle/publish-config.gradle')
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package grails.testing.web
20+
21+
import grails.artefact.TagLibrary
22+
import grails.core.GrailsClass
23+
import grails.core.GrailsControllerClass
24+
import grails.core.gsp.GrailsTagLibClass
25+
import grails.util.GrailsNameUtils
26+
import grails.web.mvc.FlashScope
27+
import grails.web.servlet.mvc.GrailsParameterMap
28+
import groovy.text.Template
29+
import groovy.transform.CompileDynamic
30+
import groovy.transform.CompileStatic
31+
import groovy.util.logging.Slf4j
32+
import org.grails.buffer.GrailsPrintWriter
33+
import org.grails.commons.CodecArtefactHandler
34+
import org.grails.commons.DefaultGrailsCodecClass
35+
import org.grails.core.artefact.ControllerArtefactHandler
36+
import org.grails.core.artefact.gsp.TagLibArtefactHandler
37+
import org.grails.gsp.GroovyPagesTemplateEngine
38+
import org.grails.plugins.codecs.DefaultCodecLookup
39+
import org.grails.plugins.testing.GrailsMockHttpServletRequest
40+
import org.grails.plugins.testing.GrailsMockHttpServletResponse
41+
import org.grails.taglib.TagLibraryLookup
42+
import org.grails.testing.GrailsUnitTest
43+
import org.grails.web.servlet.mvc.GrailsWebRequest
44+
import org.grails.web.util.GrailsApplicationAttributes
45+
import org.springframework.mock.web.MockHttpSession
46+
import org.springframework.mock.web.MockServletContext
47+
48+
@CompileStatic
49+
@Slf4j
50+
trait GrailsWebUnitTest implements GrailsUnitTest {
51+
52+
private Set<Class> loadedCodecs = new HashSet<Class>()
53+
static Map<String, String> groovyPages = [:]
54+
GrailsWebRequest webRequest
55+
56+
GrailsMockHttpServletRequest getRequest() {
57+
return (GrailsMockHttpServletRequest) getWebRequest().getCurrentRequest()
58+
}
59+
60+
GrailsMockHttpServletResponse getResponse() {
61+
return (GrailsMockHttpServletResponse) getWebRequest().getCurrentResponse()
62+
}
63+
64+
MockServletContext getServletContext() {
65+
(MockServletContext)optionalServletContext
66+
}
67+
68+
Map<String, String> getViews() {
69+
groovyPages
70+
}
71+
72+
/**
73+
* The {@link org.springframework.mock.web.MockHttpSession} instance
74+
*/
75+
MockHttpSession getSession() {
76+
(MockHttpSession) request.session
77+
}
78+
79+
/**
80+
* @return The status code of the response
81+
*/
82+
int getStatus() {
83+
response.status
84+
}
85+
86+
/**
87+
* The Grails 'params' object which is an instance of {@link grails.web.servlet.mvc.GrailsParameterMap}
88+
*/
89+
GrailsParameterMap getParams() {
90+
webRequest.getParams()
91+
}
92+
93+
/**
94+
* The Grails 'flash' object
95+
* @return
96+
*/
97+
FlashScope getFlash() {
98+
webRequest.getFlashScope()
99+
}
100+
101+
@CompileDynamic
102+
Object mockTagLib(Class<?> tagLibClass) {
103+
GrailsTagLibClass tagLib = grailsApplication.addArtefact(TagLibArtefactHandler.TYPE, tagLibClass)
104+
final tagLookup = applicationContext.getBean(TagLibraryLookup)
105+
106+
107+
defineBeans {
108+
"${tagLib.fullName}"(tagLibClass) { bean ->
109+
bean.autowire = true
110+
}
111+
}
112+
113+
tagLookup.registerTagLib(tagLib)
114+
115+
def taglibObject = applicationContext.getBean(tagLib.fullName)
116+
if(taglibObject instanceof TagLibrary) {
117+
((TagLibrary)taglibObject).setTagLibraryLookup(tagLookup)
118+
}
119+
taglibObject
120+
}
121+
122+
@CompileDynamic
123+
Object mockController(Class<?> controllerClass) {
124+
createAndEnhanceController(controllerClass)
125+
defineBeans {
126+
"$controllerClass.name"(controllerClass) { bean ->
127+
bean.scope = 'prototype'
128+
bean.autowire = true
129+
}
130+
}
131+
132+
def controller = applicationContext.getBean(controllerClass.name)
133+
134+
if (webRequest == null) {
135+
throw new IllegalAccessException("Cannot access the controller outside of a request. Is the controller referenced in a where: block?")
136+
}
137+
138+
webRequest.request.setAttribute(GrailsApplicationAttributes.CONTROLLER, controller)
139+
webRequest.controllerName = GrailsNameUtils.getLogicalPropertyName(controller.class.name, ControllerArtefactHandler.TYPE)
140+
141+
controller
142+
}
143+
144+
private GrailsClass createAndEnhanceController(Class controllerClass) {
145+
final GrailsControllerClass controllerArtefact = (GrailsControllerClass) grailsApplication.addArtefact(ControllerArtefactHandler.TYPE, controllerClass)
146+
controllerArtefact.initialize()
147+
return controllerArtefact
148+
}
149+
150+
151+
void mockTagLibs(Class<?>... tagLibClasses) {
152+
for(Class c : tagLibClasses) {
153+
mockTagLib c
154+
}
155+
}
156+
157+
void mockCodec(Class<?> codecClass, boolean reinitialize = true) {
158+
if (loadedCodecs.contains(codecClass)) {
159+
return
160+
}
161+
loadedCodecs << codecClass
162+
DefaultGrailsCodecClass grailsCodecClass = new DefaultGrailsCodecClass(codecClass)
163+
grailsCodecClass.configureCodecMethods()
164+
grailsApplication.addArtefact(CodecArtefactHandler.TYPE, grailsCodecClass)
165+
if (reinitialize) {
166+
applicationContext.getBean(DefaultCodecLookup).reInitialize()
167+
}
168+
}
169+
170+
/**
171+
* Mimics the behavior of the render method in controllers but returns the rendered contents directly
172+
*
173+
* @param args The same arguments as the controller render method accepts
174+
* @return The resulting rendering GSP
175+
*/
176+
String render(Map args) {
177+
String uri = null
178+
Map model
179+
if (args.containsKey('model')) {
180+
model = (Map)args.model
181+
} else {
182+
model = [:]
183+
}
184+
final attributes = webRequest.attributes
185+
if (args.template) {
186+
uri = attributes.getTemplateUri(args.template as String, request)
187+
}
188+
else if (args.view) {
189+
uri = attributes.getViewUri(args.view as String, request)
190+
}
191+
if (uri != null) {
192+
GroovyPagesTemplateEngine engine = applicationContext.getBean(GroovyPagesTemplateEngine)
193+
final Template t = engine.createTemplate(uri)
194+
if (t != null) {
195+
def sw = new StringWriter()
196+
renderTemplateToStringWriter(sw, t, model)
197+
return sw.toString()
198+
}
199+
}
200+
return null
201+
}
202+
203+
/**
204+
* Renders a template for the given contents and model
205+
*
206+
* @param contents The contents
207+
* @param model The model
208+
* @return The rendered template
209+
*/
210+
String applyTemplate(String contents, Map model = [:]) {
211+
def sw = new StringWriter()
212+
applyTemplate sw, contents, model
213+
return sw.toString()
214+
}
215+
216+
/**
217+
* Renders a template for the given contents and model to the provided writer
218+
*
219+
* @param sw The write to write the rendered template to
220+
* @param contents The contents
221+
* @param model The model
222+
*/
223+
void applyTemplate(StringWriter sw, String template, Map params = [:]) {
224+
def engine = applicationContext.getBean(GroovyPagesTemplateEngine)
225+
226+
def t = engine.createTemplate(template, "test_" + System.currentTimeMillis())
227+
renderTemplateToStringWriter(sw, t, params)
228+
}
229+
230+
private renderTemplateToStringWriter(StringWriter sw, Template t, Map params) {
231+
if (!webRequest.controllerName) {
232+
webRequest.controllerName = 'test'
233+
}
234+
if (!webRequest.actionName) {
235+
webRequest.actionName = 'index'
236+
}
237+
def w = t.make(params)
238+
def previousOut = webRequest.out
239+
try {
240+
def out = new GrailsPrintWriter(sw)
241+
webRequest.out = out
242+
w.writeTo(out)
243+
244+
}
245+
finally {
246+
webRequest.out = previousOut
247+
}
248+
}
249+
}

0 commit comments

Comments
 (0)