You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ma...@apache.org on 2026/03/01 07:48:21 UTC
(grails-core) branch feat/http-client-testing-support updated (fce9b3c9be -> a7c66ebd52)
This is an automated email from the ASF dual-hosted git repository.
matrei pushed a change to branch feat/http-client-testing-support
in repository https://gitbox.apache.org/repos/asf/grails-core.git
discard fce9b3c9be feat: httpclient testing support
new a7c66ebd52 feat: httpclient testing support
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (fce9b3c9be)
\
N -- N -- N refs/heads/feat/http-client-testing-support (a7c66ebd52)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
gradle/functional-test-config.gradle | 2 -
.../binding/AdvancedDataBindingSpec.groovy | 48 +++++++++++-----------
.../codecs/SecurityCodecsSpec.groovy | 16 ++++----
.../commanddi/CommandObjectDISpec.groovy | 22 +++++-----
.../InterceptorAdvancedMatchingSpec.groovy | 12 +++---
.../interceptors/InterceptorOrderingSpec.groovy | 22 +++++-----
.../requestresponse/RequestResponseSpec.groovy | 26 ++++++------
.../functionaltests/taglib/TagLibSpec.groovy | 18 ++++----
.../groovy/functional/tests/BookSpec.groovy | 33 +++++----------
.../groovy/functional/tests/TeamSpec.groovy | 12 ++----
.../functional/tests/TestGsonControllerSpec.groovy | 16 ++++----
11 files changed, 106 insertions(+), 121 deletions(-)
(grails-core) 01/01: feat: httpclient testing support
Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
matrei pushed a commit to branch feat/http-client-testing-support
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit a7c66ebd5269a0060297599b54cb53aa1c72d74c
Author: Mattias Reichel <ma...@apache.org>
AuthorDate: Sun Mar 1 08:17:24 2026 +0100
feat: httpclient testing support
---
.github/workflows/gradle.yml | 2 +-
DEVELOPMENT.md | 2 +-
dependencies.gradle | 31 +-
gradle/functional-test-config.gradle | 5 +-
gradle/publish-root-config.gradle | 1 +
grails-test-examples/app1/build.gradle | 3 +-
.../groovy/functionaltests/AtResourceSpec.groovy | 23 +-
.../BookRestfulControllerSpec.groovy | 24 +-
.../functionaltests/async/AsyncPromiseSpec.groovy | 62 ++--
.../binding/AdvancedDataBindingSpec.groovy | 114 ++++----
.../functionaltests/caching/CachingSpec.groovy | 55 ++--
.../codecs/SecurityCodecsSpec.groovy | 90 +++---
.../commanddi/CommandObjectDISpec.groovy | 94 +++---
.../contentneg/ContentNegotiationSpec.groovy | 58 ++--
.../functionaltests/cors/CorsAdvancedSpec.groovy | 48 ++-
.../errorhandling/ErrorHandlingSpec.groovy | 33 +--
.../fileupload/FileUploadSpec.groovy | 46 ++-
.../flow/FlashChainForwardSpec.groovy | 61 ++--
.../i18n/InternationalizationSpec.groovy | 72 ++---
.../InterceptorAdvancedMatchingSpec.groovy | 71 ++---
.../interceptors/InterceptorOrderingSpec.groovy | 91 +++---
.../requestresponse/RequestResponseSpec.groovy | 95 +++---
.../springevents/SpringEventsSpec.groovy | 50 ++--
.../functionaltests/taglib/TagLibSpec.groovy | 150 +++++-----
.../urlmappings/UrlMappingsSpec.groovy | 56 ++--
.../async-events-pubsub-demo/build.gradle | 4 +-
.../groovy/pubsub/demo/TaskControllerSpec.groovy | 34 +--
grails-test-examples/cache/build.gradle | 8 +-
.../com/demo/AdvancedCachingIntegrationSpec.groovy | 117 ++------
.../grails-multiple-datasources/build.gradle | 8 +-
.../MultiDataSourceWithSessionSpec.groovy | 34 +--
.../hibernate5/issue450/build.gradle | 3 +-
.../groovy/example/BookControllerSpec.groovy | 27 +-
grails-test-examples/issue-11102/build.gradle | 6 +-
.../groovy/issue11102/HttpClientCommonSpec.groovy | 45 ---
.../groovy/issue11102/TestControllerSpec.groovy | 22 +-
grails-test-examples/issue-11767/build.gradle | 7 +-
.../groovy/issue11767/app/ConfigLoadingSpec.groovy | 35 +--
grails-test-examples/issue-15228/build.gradle | 3 +-
.../issue11767/app/GsonViewRespondSpec.groovy | 43 ++-
grails-test-examples/issue-views-182/build.gradle | 3 +-
.../groovy/views182/CustomErrorSpec.groovy | 25 +-
.../groovy/views182/HttpClientCommonSpec.groovy | 46 ---
grails-test-examples/micronaut/build.gradle | 2 +-
.../MicronautDeclarativeClientSpec.groovy | 57 +---
.../micronaut/MicronautErsatzAdvancedSpec.groovy | 324 +--------------------
.../micronaut/MicronautErsatzPatternSpec.groovy | 36 +--
.../micronaut/MicronautErsatzRoundtripSpec.groovy | 98 +------
.../views-functional-tests/build.gradle | 3 +-
.../groovy/functional/tests/BookSpec.groovy | 83 +++---
.../groovy/functional/tests/BulletinSpec.groovy | 22 +-
.../groovy/functional/tests/CircularSpec.groovy | 44 ++-
.../groovy/functional/tests/CustomerSpec.groovy | 31 +-
.../groovy/functional/tests/EmbeddedSpec.groovy | 39 +--
.../groovy/functional/tests/HttpClientSpec.groovy | 44 ---
.../groovy/functional/tests/InheritanceSpec.groovy | 24 +-
.../tests/ModelInterceptorIntSpec.groovy | 35 +--
.../functional/tests/ObjectTemplateSpec.groovy | 25 +-
.../functional/tests/PersonInheritanceSpec.groovy | 24 +-
.../groovy/functional/tests/ProductSpec.groovy | 99 +++----
.../groovy/functional/tests/ProjectSpec.groovy | 33 +--
.../groovy/functional/tests/ProxySpec.groovy | 25 +-
.../groovy/functional/tests/TeamSpec.groovy | 45 +--
.../functional/tests/TestControllerSpec.groovy | 24 +-
.../functional/tests/TestGmlControllerSpec.groovy | 32 +-
.../functional/tests/TestGsonControllerSpec.groovy | 91 +++---
.../groovy/functional/tests/VehicleSpec.groovy | 37 +--
.../functional/tests/api/NamespacedBookSpec.groovy | 70 ++---
.../build.gradle | 40 ++-
.../httpclient/HttpClientConfigurationUtils.groovy | 120 ++++++++
.../testing/httpclient/HttpClientSupport.groovy | 172 +++++++++++
.../HttpClientConfigurationUtilsSpec.groovy | 77 +++++
.../httpclient/HttpClientSupportSpec.groovy | 91 ++++++
settings.gradle | 9 +-
74 files changed, 1515 insertions(+), 2148 deletions(-)
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index bcc40c1a21..ff63b06408 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -228,7 +228,7 @@ jobs:
--continue
--rerun-tasks
--stacktrace
- -PgebAtCheckWaiting
+ -PextendTestClientTimeouts
-PgrailsIndy=${{ matrix.indy }}
-PonlyFunctionalTests
-PskipCodeStyle
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index 43541a0ce4..906adf1a5e 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -31,7 +31,7 @@ These can be set on the command line like so:
`./gradlew check -PskipCodeStyle`
-* `gebAtCheckWaiting` - enables Geb atCheckWaiting
+* `extendTestClientTimeouts` - enables Geb atCheckWaiting, and sets longer timeouts for Geb and Micronaut Http Client
* `onlyCoreTests` - runs tests that do not include mongo, hibernate, or functional
* `onlyFunctionalTests` - runs only grails-test-examples/* tests
* `onlyHibernate5Tests` - runs only a hibernate5 related test
diff --git a/dependencies.gradle b/dependencies.gradle
index ecbbca0f2e..d87ca91b67 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -77,6 +77,7 @@ ext {
'jackson.version' : '2.19.1',
'jquery.version' : '3.7.1',
'liquibase-hibernate5.version': '4.27.0',
+ 'micronaut.core.version' : '4.9.9',
'mongodb.version' : '5.5.1',
'rxjava.version' : '1.3.8',
'rxjava2.version' : '2.2.21',
@@ -135,20 +136,22 @@ ext {
'groovy-xml' : "org.apache.groovy:groovy-xml:${bomDependencyVersions['groovy.version']}",
'groovy-yaml' : "org.apache.groovy:groovy-yaml:${bomDependencyVersions['groovy.version']}",
// end - restate the groovy-bom here because the spring dependency management
- 'jquery' : "org.webjars.npm:jquery:${bomDependencyVersions['jquery.version']}",
- 'liquibase-hibernate5' : "org.liquibase:liquibase:${bomDependencyVersions['liquibase-hibernate5.version']}",
- 'liquibase-hibernate5-cdi' : "org.liquibase:liquibase-cdi:${bomDependencyVersions['liquibase-hibernate5.version']}",
- 'liquibase-hibernate5-core': "org.liquibase:liquibase-core:${bomDependencyVersions['liquibase-hibernate5.version']}",
- 'liquibase-hibernate5-ext' : "org.liquibase.ext:liquibase-hibernate5:${bomDependencyVersions['liquibase-hibernate5.version']}",
- 'mongodb-bson' : "org.mongodb:bson:${bomDependencyVersions['mongodb.version']}",
- 'mongodb-driver-core' : "org.mongodb:mongodb-driver-core:${bomDependencyVersions['mongodb.version']}",
- 'mongodb-driver-sync' : "org.mongodb:mongodb-driver-sync:${bomDependencyVersions['mongodb.version']}",
- 'mongodb-record-codec' : "org.mongodb:bson-record-codec:${bomDependencyVersions['mongodb.version']}",
- 'rxjava' : "io.reactivex:rxjava:${bomDependencyVersions['rxjava.version']}",
- 'rxjava2' : "io.reactivex.rxjava2:rxjava:${bomDependencyVersions['rxjava2.version']}",
- 'rxjava3' : "io.reactivex.rxjava3:rxjava:${bomDependencyVersions['rxjava3.version']}",
- 'sitemesh' : "opensymphony:sitemesh:${bomDependencyVersions['sitemesh.version']}",
- 'starter-sitemesh' : "org.sitemesh:spring-boot-starter-sitemesh:${bomDependencyVersions['starter-sitemesh.version']}",
+ 'jquery' : "org.webjars.npm:jquery:${bomDependencyVersions['jquery.version']}",
+ 'liquibase-hibernate5' : "org.liquibase:liquibase:${bomDependencyVersions['liquibase-hibernate5.version']}",
+ 'liquibase-hibernate5-cdi' : "org.liquibase:liquibase-cdi:${bomDependencyVersions['liquibase-hibernate5.version']}",
+ 'liquibase-hibernate5-core' : "org.liquibase:liquibase-core:${bomDependencyVersions['liquibase-hibernate5.version']}",
+ 'liquibase-hibernate5-ext' : "org.liquibase.ext:liquibase-hibernate5:${bomDependencyVersions['liquibase-hibernate5.version']}",
+ 'micronaut-http-client' : "io.micronaut:micronaut-http-client:${bomDependencyVersions['micronaut.core.version']}",
+ 'micronaut-jackson-databind' : "io.micronaut:micronaut-jackson-databind:${bomDependencyVersions['micronaut.core.version']}",
+ 'mongodb-bson' : "org.mongodb:bson:${bomDependencyVersions['mongodb.version']}",
+ 'mongodb-driver-core' : "org.mongodb:mongodb-driver-core:${bomDependencyVersions['mongodb.version']}",
+ 'mongodb-driver-sync' : "org.mongodb:mongodb-driver-sync:${bomDependencyVersions['mongodb.version']}",
+ 'mongodb-record-codec' : "org.mongodb:bson-record-codec:${bomDependencyVersions['mongodb.version']}",
+ 'rxjava' : "io.reactivex:rxjava:${bomDependencyVersions['rxjava.version']}",
+ 'rxjava2' : "io.reactivex.rxjava2:rxjava:${bomDependencyVersions['rxjava2.version']}",
+ 'rxjava3' : "io.reactivex.rxjava3:rxjava:${bomDependencyVersions['rxjava3.version']}",
+ 'sitemesh' : "opensymphony:sitemesh:${bomDependencyVersions['sitemesh.version']}",
+ 'starter-sitemesh' : "org.sitemesh:spring-boot-starter-sitemesh:${bomDependencyVersions['starter-sitemesh.version']}",
]
// Because pom exclusions aren't properly supported by gradle, we can't inherit the grails-gradle-bom
diff --git a/gradle/functional-test-config.gradle b/gradle/functional-test-config.gradle
index 55e9798284..922f9c63c6 100644
--- a/gradle/functional-test-config.gradle
+++ b/gradle/functional-test-config.gradle
@@ -115,9 +115,10 @@ tasks.withType(Test).configureEach { Test task ->
}
// Make Geb tests more resilient in slow CI environments
- if (project.hasProperty('gebAtCheckWaiting')) {
+ if (project.hasProperty('extendTestClientTimeouts')) {
systemProperty('grails.geb.atCheckWaiting.enabled', 'true')
- systemProperty('grails.geb.timeouts.timeout', '10')
+ systemProperty('grails.geb.timeouts.timeout', '10') // default = 5
+ systemProperty('micronaut.http.client.read-timeout', '20s') // default = 10s
}
}
diff --git a/gradle/publish-root-config.gradle b/gradle/publish-root-config.gradle
index 1b56b0ccb8..b61cdab48c 100644
--- a/gradle/publish-root-config.gradle
+++ b/gradle/publish-root-config.gradle
@@ -79,6 +79,7 @@ def publishedProjects = [
'grails-testing-support-dbcleanup-core',
'grails-testing-support-dbcleanup-h2',
'grails-testing-support-dbcleanup-postgresql',
+ 'grails-testing-support-httpclient',
'grails-testing-support-mongodb',
'grails-testing-support-views-gson',
'grails-testing-support-web',
diff --git a/grails-test-examples/app1/build.gradle b/grails-test-examples/app1/build.gradle
index c2c185eff5..18bc982ff5 100644
--- a/grails-test-examples/app1/build.gradle
+++ b/grails-test-examples/app1/build.gradle
@@ -61,14 +61,13 @@ dependencies {
testImplementation 'org.apache.grails:grails-testing-support-datamapping'
console 'org.apache.grails:grails-console'
- testImplementation "io.micronaut:micronaut-http-client:$micronautHttpClientVersion"
- testImplementation "io.micronaut.serde:micronaut-serde-jackson:$micronautSerdeJacksonVersion"
implementation project(':grails-test-examples-plugins-loadfirst')
implementation project(':grails-test-examples-plugins-loadsecond')
implementation project(':grails-test-examples-plugins-loadafter')
integrationTestImplementation testFixtures('org.apache.grails:grails-geb')
+ integrationTestImplementation 'org.apache.grails:grails-testing-support-httpclient'
}
tasks.withType(JavaExec).configureEach {
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/AtResourceSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/AtResourceSpec.groovy
index bdb8d1b30c..31df034904 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/AtResourceSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/AtResourceSpec.groovy
@@ -16,34 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functionaltests
-import grails.testing.mixin.integration.Integration
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import io.micronaut.http.client.HttpClient
import spock.lang.Specification
-@Integration(applicationClass = Application)
-class AtResourceSpec extends Specification {
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- def "A domain class annotated with @Resources exposes an endpoint"() {
- given:
- String baseUrl = "http://localhost:$serverPort"
- HttpClient client = HttpClient.create(new URL(baseUrl))
+@Integration
+class AtResourceSpec extends Specification implements HttpClientSupport {
+ def "A domain class annotated with @Resources exposes an endpoint"() {
when:
- HttpResponse<String> rsp = client.toBlocking().exchange(HttpRequest.GET("/stuffs"), String)
+ def rsp = httpClient.exchange('/stuffs', String)
then:
noExceptionThrown()
rsp.status() == HttpStatus.OK
-
- cleanup:
- client.close()
}
-
-
}
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/BookRestfulControllerSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/BookRestfulControllerSpec.groovy
index 59f77e83b3..bacd675227 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/BookRestfulControllerSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/BookRestfulControllerSpec.groovy
@@ -16,40 +16,32 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functionaltests
-import grails.testing.mixin.integration.Integration
import groovy.json.JsonSlurper
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
+
import io.micronaut.http.HttpStatus
-import io.micronaut.http.client.HttpClient
import spock.lang.Specification
-@Integration(applicationClass = Application)
-class BookRestfulControllerSpec extends Specification {
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- def "A RestfulController exposes an index endpoint for a domain class"() {
- given:
- String baseUrl = "http://localhost:$serverPort"
- HttpClient client = HttpClient.create(new URL(baseUrl))
+@Integration
+class BookRestfulControllerSpec extends Specification implements HttpClientSupport {
+ def "A RestfulController exposes an index endpoint for a domain class"() {
when:
- HttpResponse<String> rsp = client.toBlocking().exchange(HttpRequest.GET("/bookRestful"), String)
+ def rsp = httpClient.exchange('/bookRestful', String)
then:
noExceptionThrown()
rsp.status() == HttpStatus.OK
and: 'the book persisted in BootStrap is retrieved'
- List<Map<String, Object>> json = new JsonSlurper().parseText(rsp.body())
+ def json = new JsonSlurper().parseText(rsp.body()) as List<Map<String, Object>>
json[0]['id'] != null
json[0]['title'] == 'Example Book'
json[0]['lastUpdated'] != null
json[0]['dateCreated'] != null
-
- cleanup:
- client.close()
}
}
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/async/AsyncPromiseSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/async/AsyncPromiseSpec.groovy
index 43f3feb750..b943138988 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/async/AsyncPromiseSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/async/AsyncPromiseSpec.groovy
@@ -16,7 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functionaltests.async
import java.util.concurrent.TimeUnit
@@ -27,39 +26,30 @@ import functionaltests.services.AsyncProcessingService
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
-import io.micronaut.http.client.HttpClient
-import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
+import org.springframework.beans.factory.annotation.Autowired
+
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for async/promise functionality in Grails.
* Tests various async patterns including tasks, promises, chaining, and error handling.
*/
@Integration
-class AsyncPromiseSpec extends Specification {
-
- @Shared
- HttpClient client
+class AsyncPromiseSpec extends Specification implements HttpClientSupport {
+ @Autowired
AsyncProcessingService asyncProcessingService
- def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:${serverPort}"))
- }
-
- def cleanupSpec() {
- client.close()
- }
-
// ========== Basic Async Task Tests ==========
def "simple async task completes successfully"() {
when: "calling simple task endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/asyncTest/simpleTask'),
+ def response = httpClient.exchange(
+ '/asyncTest/simpleTask',
String
)
def json = new JsonSlurper().parseText(response.body())
@@ -75,8 +65,8 @@ class AsyncPromiseSpec extends Specification {
def value = 7
when: "calling compute endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET("/asyncTest/computeTask?value=${value}"),
+ def response = httpClient.exchange(
+ "/asyncTest/computeTask?value=${value}",
String
)
def json = new JsonSlurper().parseText(response.body())
@@ -89,8 +79,8 @@ class AsyncPromiseSpec extends Specification {
def "parallel tasks complete and return all results"() {
when: "calling parallel tasks endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/asyncTest/parallelTasks'),
+ def response = httpClient.exchange(
+ '/asyncTest/parallelTasks',
String
)
def json = new JsonSlurper().parseText(response.body())
@@ -109,8 +99,8 @@ class AsyncPromiseSpec extends Specification {
def input = 'hello'
when: "calling chained endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET("/asyncTest/chainedTasks?input=${input}"),
+ def response = httpClient.exchange(
+ "/asyncTest/chainedTasks?input=${input}",
String
)
def json = new JsonSlurper().parseText(response.body())
@@ -126,8 +116,8 @@ class AsyncPromiseSpec extends Specification {
def "async task handles success without error"() {
when: "calling task that should succeed"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/asyncTest/taskWithError?fail=false'),
+ def response = httpClient.exchange(
+ '/asyncTest/taskWithError?fail=false',
String
)
def json = new JsonSlurper().parseText(response.body())
@@ -141,7 +131,7 @@ class AsyncPromiseSpec extends Specification {
@Unroll
def "async task with timeout completes within time limit"(int delay, int timeout, int elapsedMin, String status, String result) {
when: "calling task with reasonable timeout"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET("/asyncTest/taskWithTimeout?delay=$delay&timeout=$timeout"),
String
)
@@ -166,7 +156,7 @@ class AsyncPromiseSpec extends Specification {
def input = 'test'
when: "calling async service endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET("/asyncTest/useAsyncService?input=${input}"),
String
)
@@ -183,7 +173,7 @@ class AsyncPromiseSpec extends Specification {
def value = 6
when: "calling async calculation endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET("/asyncTest/asyncCalculation?value=${value}"),
String
)
@@ -197,7 +187,7 @@ class AsyncPromiseSpec extends Specification {
def "async batch processing reverses all items"() {
when: "calling batch endpoint with default items"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/asyncTest/asyncBatch'),
String
)
@@ -216,7 +206,7 @@ class AsyncPromiseSpec extends Specification {
def taskId = 'task-123'
when: "calling long running endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET("/asyncTest/longRunning?taskId=${taskId}"),
String
)
@@ -236,7 +226,7 @@ class AsyncPromiseSpec extends Specification {
def v2 = 4
when: "calling compose endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET("/asyncTest/composeFutures?v1=${v1}&v2=${v2}"),
String
)
@@ -256,7 +246,7 @@ class AsyncPromiseSpec extends Specification {
def body = '{"name": "test", "value": "hello"}'
when: "posting to process endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/asyncTest/processRequestData', body)
.contentType(MediaType.APPLICATION_JSON),
String
@@ -272,7 +262,7 @@ class AsyncPromiseSpec extends Specification {
def "multi-stage process reports all stages"() {
when: "calling multi-stage endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/asyncTest/multiStageProcess'),
String
)
@@ -290,7 +280,7 @@ class AsyncPromiseSpec extends Specification {
def "stages execute in correct order"() {
when: "calling multi-stage endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/asyncTest/multiStageProcess'),
String
)
@@ -310,7 +300,7 @@ class AsyncPromiseSpec extends Specification {
def input = 'grails'
when: "calling with async=true"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET("/asyncTest/conditionalAsync?async=true&input=${input}"),
String
)
@@ -328,7 +318,7 @@ class AsyncPromiseSpec extends Specification {
def input = 'grails'
when: "calling with async=false"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET("/asyncTest/conditionalAsync?async=false&input=${input}"),
String
)
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/binding/AdvancedDataBindingSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/binding/AdvancedDataBindingSpec.groovy
index b3e3777ab9..035b034213 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/binding/AdvancedDataBindingSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/binding/AdvancedDataBindingSpec.groovy
@@ -20,11 +20,10 @@ package functionaltests.binding
import io.micronaut.http.HttpRequest
import io.micronaut.http.MediaType
-import io.micronaut.http.client.HttpClient
-import spock.lang.Shared
import spock.lang.Specification
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Comprehensive integration tests for advanced data binding features.
@@ -45,25 +44,14 @@ import grails.testing.mixin.integration.Integration
* - String trimming
*/
@Integration
-class AdvancedDataBindingSpec extends Specification {
-
- @Shared
- HttpClient client
-
- def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:$serverPort"))
- }
-
- def cleanupSpec() {
- client.close()
- }
+class AdvancedDataBindingSpec extends Specification implements HttpClientSupport {
// ========== Map-Based Binding Tests ==========
def "test basic map-based binding"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindEmployee?firstName=John&lastName=Doe&salary=50000'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindEmployee?firstName=John&lastName=Doe&salary=50000',
Map
)
@@ -76,8 +64,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test nested object binding"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindEmployee?firstName=Jane&homeAddress.street=123+Main+St&homeAddress.city=Springfield&homeAddress.state=IL'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindEmployee?firstName=Jane&homeAddress.street=123+Main+St&homeAddress.city=Springfield&homeAddress.state=IL',
Map
)
@@ -94,8 +82,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test @BindUsing annotation lowercases and trims email"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindWithBindUsing?email=John.Doe%40Example.COM'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindWithBindUsing?email=John.Doe%40Example.COM',
Map
)
@@ -107,8 +95,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test @BindUsing with mixed case email"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindWithBindUsing?email=TEST.User%40DOMAIN.org'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindWithBindUsing?email=TEST.User%40DOMAIN.org',
Map
)
@@ -121,8 +109,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test @BindingFormat for date parsing - MMddyyyy format"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindWithDateFormat?hireDate=01152020'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindWithDateFormat?hireDate=01152020',
Map
)
@@ -134,8 +122,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test @BindingFormat for date parsing - yyyy-MM-dd format"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindWithDateFormat?birthDate=1990-05-20'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindWithDateFormat?birthDate=1990-05-20',
Map
)
@@ -147,8 +135,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test multiple date formats in same request"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindWithDateFormat?hireDate=03012021&birthDate=1985-12-25'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindWithDateFormat?hireDate=03012021&birthDate=1985-12-25',
Map
)
@@ -162,8 +150,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test binding to List collection"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindTeamWithMembers?name=Engineering&members%5B0%5D.name=Alice&members%5B0%5D.role=Lead&members%5B1%5D.name=Bob&members%5B1%5D.role=Developer'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindTeamWithMembers?name=Engineering&members%5B0%5D.name=Alice&members%5B0%5D.role=Lead&members%5B1%5D.name=Bob&members%5B1%5D.role=Developer',
Map
)
@@ -179,8 +167,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test binding to List with gaps in indices"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindTeamWithMembers?name=QA&members%5B0%5D.name=Carol&members%5B2%5D.name=Dave'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindTeamWithMembers?name=QA&members%5B0%5D.name=Carol&members%5B2%5D.name=Dave',
Map
)
@@ -197,8 +185,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test binding to Map collection"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindProjectWithContributors?name=GrailsCore&contributors%5Blead%5D.name=John&contributors%5Blead%5D.expertise=Architecture&contributors%5Bdev%5D.name=Jane&contributors%5Bdev%5D.expertise=Testing'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindProjectWithContributors?name=GrailsCore&contributors%5Blead%5D.name=John&contributors%5Blead%5D.expertise=Architecture&contributors%5Bdev%5D.name=Jane&contributors%5Bdev%5D.expertise=Testing',
Map
)
@@ -215,8 +203,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test @RequestParameter maps different parameter names"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindWithRequestParameter?firstName=Robert&lastName=Smith&age=30'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindWithRequestParameter?firstName=Robert&lastName=Smith&age=30',
Map
)
@@ -231,8 +219,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test bindData with include - only specified properties bound"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindWithIncludeExclude?firstName=Test&lastName=User&email=test%40example.com&salary=100000'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindWithIncludeExclude?firstName=Test&lastName=User&email=test%40example.com&salary=100000',
Map
)
@@ -248,8 +236,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test selective property binding using subscript operator"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindSelectiveProperties?firstName=Selective&lastName=Test&email=should.not.bind%40test.com&salary=999'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindSelectiveProperties?firstName=Selective&lastName=Test&email=should.not.bind%40test.com&salary=999',
Map
)
@@ -265,8 +253,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test using grailsWebDataBinder directly"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindUsingDirectBinder?firstName=Direct&lastName=Binder&email=DIRECT%40TEST.COM'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindUsingDirectBinder?firstName=Direct&lastName=Binder&email=DIRECT%40TEST.COM',
Map
)
@@ -282,8 +270,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test command object binding with validation - valid data"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindCommandObject?firstName=Valid&lastName=User&email=valid%40email.com'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindCommandObject?firstName=Valid&lastName=User&email=valid%40email.com',
Map
)
@@ -298,8 +286,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test command object binding with validation - invalid data"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindCommandObject?firstName=&lastName=&email=invalid-email'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindCommandObject?firstName=&lastName=&email=invalid-email',
Map
)
@@ -312,8 +300,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test nested command object binding"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindNestedCommandObject?name=Contact+Person&address.street=456+Oak+Ave&address.city=Portland'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindNestedCommandObject?name=Contact+Person&address.street=456+Oak+Ave&address.city=Portland',
Map
)
@@ -328,7 +316,7 @@ class AdvancedDataBindingSpec extends Specification {
def "test JSON body binding to command object"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/advancedDataBinding/bindJsonBody', [
firstName: 'JsonFirst',
lastName: 'JsonLast',
@@ -349,8 +337,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test binding multiple command objects"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindMultipleCommandObjects?employee.firstName=Multi&employee.lastName=Test&address.street=789+Pine+Rd&address.city=Seattle'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindMultipleCommandObjects?employee.firstName=Multi&employee.lastName=Test&address.street=789+Pine+Rd&address.city=Seattle',
Map
)
@@ -366,8 +354,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test empty string converts to null"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindEmptyStrings?firstName=&lastName=HasValue'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindEmptyStrings?firstName=&lastName=HasValue',
Map
)
@@ -382,8 +370,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test string trimming during binding"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindWithTrimming?firstName=+++Trimmed+++'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindWithTrimming?firstName=+++Trimmed+++',
Map
)
@@ -397,8 +385,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test valid type conversion"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindWithTypeConversion?salary=75000&firstName=TypeTest'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindWithTypeConversion?salary=75000&firstName=TypeTest',
Map
)
@@ -412,8 +400,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test binding with special characters in values"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindEmployee?firstName=O%27Brien&lastName=M%C3%BCller'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindEmployee?firstName=O%27Brien&lastName=M%C3%BCller',
Map
)
@@ -425,8 +413,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test binding with unicode characters"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindEmployee?firstName=%E6%97%A5%E6%9C%AC%E8%AA%9E'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindEmployee?firstName=%E6%97%A5%E6%9C%AC%E8%AA%9E',
Map
)
@@ -437,8 +425,8 @@ class AdvancedDataBindingSpec extends Specification {
def "test binding with null parameter values"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/advancedDataBinding/bindEmployee?firstName=TestNull'),
+ def response = httpClient.exchange(
+ '/advancedDataBinding/bindEmployee?firstName=TestNull',
Map
)
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/caching/CachingSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/caching/CachingSpec.groovy
index 3ab4ad36c0..5d990b8f49 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/caching/CachingSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/caching/CachingSpec.groovy
@@ -16,21 +16,19 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functionaltests.caching
import groovy.json.JsonSlurper
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
-import io.micronaut.http.client.HttpClient
import spock.lang.Narrative
-import spock.lang.Shared
import spock.lang.Specification
import org.springframework.beans.factory.annotation.Autowired
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for Grails caching with @Cacheable, @CacheEvict, @CachePut.
@@ -48,27 +46,18 @@ Grails caching provides method-level caching via annotations @Cacheable,
@CacheEvict, and @CachePut. This allows expensive operations to be cached
and only recomputed when necessary.
''')
-class CachingSpec extends Specification {
+class CachingSpec extends Specification implements HttpClientSupport {
@Autowired
CacheTestService cacheTestService
- @Shared
- HttpClient client
-
def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:$serverPort"))
-
// Evict all caches before each test to ensure clean state
cacheTestService.evictBasicCache()
cacheTestService.evictAllFromParamCache()
cacheTestService.evictAllKeyedCache()
}
- def cleanupSpec() {
- client.close()
- }
-
// ========== Basic @Cacheable Tests - Data Consistency ==========
def "cached method returns same result on subsequent calls"() {
@@ -280,14 +269,14 @@ class CachingSpec extends Specification {
def "basic cache works via HTTP"() {
// Evict cache to start fresh
- client.toBlocking().exchange(HttpRequest.GET('/cacheTest/evictBasic'), String)
+ httpClient.exchange(HttpRequest.GET('/cacheTest/evictBasic'), String)
when: "calling endpoint twice"
- def response1 = client.toBlocking().exchange(
+ def response1 = httpClient.exchange(
HttpRequest.GET('/cacheTest/basicData'),
String
)
- def response2 = client.toBlocking().exchange(
+ def response2 = httpClient.exchange(
HttpRequest.GET('/cacheTest/basicData'),
String
)
@@ -304,14 +293,14 @@ class CachingSpec extends Specification {
def "parameter cache works via HTTP"() {
// Evict cache
- client.toBlocking().exchange(HttpRequest.GET('/cacheTest/evictAllParam'), String)
+ httpClient.exchange(HttpRequest.GET('/cacheTest/evictAllParam'), String)
when: "calling with same ID twice"
- def response1 = client.toBlocking().exchange(
+ def response1 = httpClient.exchange(
HttpRequest.GET('/cacheTest/dataById?id=42'),
String
)
- def response2 = client.toBlocking().exchange(
+ def response2 = httpClient.exchange(
HttpRequest.GET('/cacheTest/dataById?id=42'),
String
)
@@ -325,17 +314,17 @@ class CachingSpec extends Specification {
def "eviction works via HTTP"() {
// Evict cache and populate it
- client.toBlocking().exchange(HttpRequest.GET('/cacheTest/evictBasic'), String)
- def firstCall = client.toBlocking().exchange(
+ httpClient.exchange(HttpRequest.GET('/cacheTest/evictBasic'), String)
+ def firstCall = httpClient.exchange(
HttpRequest.GET('/cacheTest/basicData'),
String
)
def firstData = new JsonSlurper().parseText(firstCall.body()).data
when: "evicting via HTTP then calling again after delay"
- client.toBlocking().exchange(HttpRequest.GET('/cacheTest/evictBasic'), String)
+ httpClient.exchange(HttpRequest.GET('/cacheTest/evictBasic'), String)
Thread.sleep(10) // Ensure timestamp changes
- def afterEvict = client.toBlocking().exchange(
+ def afterEvict = httpClient.exchange(
HttpRequest.GET('/cacheTest/basicData'),
String
)
@@ -347,12 +336,12 @@ class CachingSpec extends Specification {
def "different IDs return different cached values via HTTP"() {
given:
- client.toBlocking().exchange(HttpRequest.GET('/cacheTest/evictAllParam'), String)
+ httpClient.exchange(HttpRequest.GET('/cacheTest/evictAllParam'), String)
when: "fetching different IDs"
- def response1 = client.toBlocking().exchange(HttpRequest.GET('/cacheTest/dataById?id=100'), String)
- def response2 = client.toBlocking().exchange(HttpRequest.GET('/cacheTest/dataById?id=200'), String)
- def response3 = client.toBlocking().exchange(HttpRequest.GET('/cacheTest/dataById?id=100'), String)
+ def response1 = httpClient.exchange(HttpRequest.GET('/cacheTest/dataById?id=100'), String)
+ def response2 = httpClient.exchange(HttpRequest.GET('/cacheTest/dataById?id=200'), String)
+ def response3 = httpClient.exchange(HttpRequest.GET('/cacheTest/dataById?id=100'), String)
then: "different IDs have different data, same ID returns same data"
def json1 = new JsonSlurper().parseText(response1.body())
@@ -365,11 +354,11 @@ class CachingSpec extends Specification {
def "complex data endpoint works with caching"() {
when: "fetching complex data"
- def response1 = client.toBlocking().exchange(
+ def response1 = httpClient.exchange(
HttpRequest.GET('/cacheTest/complexData?category=electronics&page=3'),
String
)
- def response2 = client.toBlocking().exchange(
+ def response2 = httpClient.exchange(
HttpRequest.GET('/cacheTest/complexData?category=electronics&page=3'),
String
)
@@ -385,18 +374,18 @@ class CachingSpec extends Specification {
def "CachePut works via HTTP with key closure"() {
// Evict keyed cache and get initial value
- client.toBlocking().exchange(HttpRequest.GET('/cacheTest/evictAllKeyed'), String)
+ httpClient.exchange(HttpRequest.GET('/cacheTest/evictAllKeyed'), String)
def initial = new JsonSlurper().parseText(
- client.toBlocking().exchange(HttpRequest.GET('/cacheTest/byKey?key=httpkey'), String).body()
+ httpClient.exchange(HttpRequest.GET('/cacheTest/byKey?key=httpkey'), String).body()
).data
when: "updating via HTTP"
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.GET('/cacheTest/updateByKey?key=httpkey&value=HTTPUpdated'),
String
)
def afterUpdate = new JsonSlurper().parseText(
- client.toBlocking().exchange(HttpRequest.GET('/cacheTest/byKey?key=httpkey'), String).body()
+ httpClient.exchange(HttpRequest.GET('/cacheTest/byKey?key=httpkey'), String).body()
).data
then: "cache contains updated value"
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/codecs/SecurityCodecsSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/codecs/SecurityCodecsSpec.groovy
index ad19ab280e..d885f5e845 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/codecs/SecurityCodecsSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/codecs/SecurityCodecsSpec.groovy
@@ -19,11 +19,10 @@
package functionaltests.codecs
import io.micronaut.http.HttpRequest
-import io.micronaut.http.client.HttpClient
-import spock.lang.Shared
import spock.lang.Specification
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Comprehensive integration tests for Grails codec functionality.
@@ -40,25 +39,14 @@ import grails.testing.mixin.integration.Integration
* - Hash consistency verification
*/
@Integration
-class SecurityCodecsSpec extends Specification {
-
- @Shared
- HttpClient client
-
- def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:$serverPort"))
- }
-
- def cleanupSpec() {
- client.close()
- }
+class SecurityCodecsSpec extends Specification implements HttpClientSupport {
// ========== HTML Encoding Tests (XSS Prevention) ==========
def "test HTML encoding escapes dangerous tags"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/codecTest/encodeHtml?input=%3Cscript%3Ealert(%22XSS%22)%3C/script%3E'),
+ def response = httpClient.exchange(
+ '/codecTest/encodeHtml?input=%3Cscript%3Ealert(%22XSS%22)%3C/script%3E',
Map
)
@@ -73,8 +61,8 @@ class SecurityCodecsSpec extends Specification {
def "test HTML encoding escapes quotes"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/codecTest/encodeHtml?input=%22quoted%22'),
+ def response = httpClient.exchange(
+ '/codecTest/encodeHtml?input=%22quoted%22',
Map
)
@@ -86,8 +74,8 @@ class SecurityCodecsSpec extends Specification {
def "test HTML encoding escapes ampersands"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/codecTest/encodeHtml?input=foo%26bar'),
+ def response = httpClient.exchange(
+ '/codecTest/encodeHtml?input=foo%26bar',
Map
)
@@ -102,8 +90,8 @@ class SecurityCodecsSpec extends Specification {
def "test URL encoding escapes spaces and special chars"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/codecTest/encodeUrl?input=hello+world%26foo%3Dbar'),
+ def response = httpClient.exchange(
+ '/codecTest/encodeUrl?input=hello+world%26foo%3Dbar',
Map
)
@@ -120,8 +108,8 @@ class SecurityCodecsSpec extends Specification {
def "test Base64 encoding and decoding text"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/codecTest/encodeBase64?input=Hello%2C+World!'),
+ def response = httpClient.exchange(
+ '/codecTest/encodeBase64?input=Hello%2C+World!',
Map
)
@@ -134,8 +122,8 @@ class SecurityCodecsSpec extends Specification {
def "test Base64 encoding with binary data"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/codecTest/encodeBase64Binary'),
+ def response = httpClient.exchange(
+ '/codecTest/encodeBase64Binary',
Map
)
@@ -150,8 +138,8 @@ class SecurityCodecsSpec extends Specification {
def "test MD5 hashing produces consistent 32-char hex string"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/codecTest/encodeMd5?input=password123'),
+ def response = httpClient.exchange(
+ '/codecTest/encodeMd5?input=password123',
Map
)
@@ -164,8 +152,8 @@ class SecurityCodecsSpec extends Specification {
def "test MD5 bytes produces 16 bytes"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/codecTest/encodeMd5Bytes?input=password123'),
+ def response = httpClient.exchange(
+ '/codecTest/encodeMd5Bytes?input=password123',
Map
)
@@ -179,8 +167,8 @@ class SecurityCodecsSpec extends Specification {
def "test SHA1 hashing produces consistent 40-char hex string"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/codecTest/encodeSha1?input=password123'),
+ def response = httpClient.exchange(
+ '/codecTest/encodeSha1?input=password123',
Map
)
@@ -192,8 +180,8 @@ class SecurityCodecsSpec extends Specification {
def "test SHA1 bytes produces 20 bytes"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/codecTest/encodeSha1Bytes?input=password123'),
+ def response = httpClient.exchange(
+ '/codecTest/encodeSha1Bytes?input=password123',
Map
)
@@ -206,8 +194,8 @@ class SecurityCodecsSpec extends Specification {
def "test SHA256 hashing produces consistent 64-char hex string"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/codecTest/encodeSha256?input=password123'),
+ def response = httpClient.exchange(
+ '/codecTest/encodeSha256?input=password123',
Map
)
@@ -219,8 +207,8 @@ class SecurityCodecsSpec extends Specification {
def "test SHA256 bytes produces 32 bytes"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/codecTest/encodeSha256Bytes?input=password123'),
+ def response = httpClient.exchange(
+ '/codecTest/encodeSha256Bytes?input=password123',
Map
)
@@ -233,7 +221,7 @@ class SecurityCodecsSpec extends Specification {
def "test Hex encoding and decoding"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/codecTest/encodeHex?input=Hello'),
Map
)
@@ -249,7 +237,7 @@ class SecurityCodecsSpec extends Specification {
def "test JavaScript encoding escapes quotes and newlines"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET("/codecTest/encodeJavaScript"),
Map
)
@@ -267,7 +255,7 @@ class SecurityCodecsSpec extends Specification {
def "test Raw encoding preserves content without escaping"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/codecTest/encodeRaw'),
Map
)
@@ -282,7 +270,7 @@ class SecurityCodecsSpec extends Specification {
def "test chaining multiple encodings"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/codecTest/multipleEncodings'),
Map
)
@@ -298,7 +286,7 @@ class SecurityCodecsSpec extends Specification {
def "test encoding with Unicode and special characters"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/codecTest/encodeSpecialChars?input=%E6%97%A5%E6%9C%AC%E8%AA%9E+%26+%C3%A9moji+%F0%9F%91%8D+%3Ctag%3E'),
Map
)
@@ -315,7 +303,7 @@ class SecurityCodecsSpec extends Specification {
def "test encoding null values returns null safely"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/codecTest/encodeNull'),
Map
)
@@ -331,7 +319,7 @@ class SecurityCodecsSpec extends Specification {
def "test encoding empty strings"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/codecTest/encodeEmpty'),
Map
)
@@ -352,7 +340,7 @@ class SecurityCodecsSpec extends Specification {
def "test hash functions produce consistent results"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/codecTest/hashConsistency?input=test-consistency'),
Map
)
@@ -366,11 +354,11 @@ class SecurityCodecsSpec extends Specification {
def "test different inputs produce different hashes"() {
when:
- def response1 = client.toBlocking().exchange(
+ def response1 = httpClient.exchange(
HttpRequest.GET('/codecTest/hashConsistency?input=input1'),
Map
)
- def response2 = client.toBlocking().exchange(
+ def response2 = httpClient.exchange(
HttpRequest.GET('/codecTest/hashConsistency?input=input2'),
Map
)
@@ -387,7 +375,7 @@ class SecurityCodecsSpec extends Specification {
def "test MD5 produces known hash for 'hello'"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/codecTest/encodeMd5?input=hello'),
Map
)
@@ -399,7 +387,7 @@ class SecurityCodecsSpec extends Specification {
def "test SHA1 produces known hash for 'hello'"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/codecTest/encodeSha1?input=hello'),
Map
)
@@ -411,7 +399,7 @@ class SecurityCodecsSpec extends Specification {
def "test SHA256 produces known hash for 'hello'"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/codecTest/encodeSha256?input=hello'),
Map
)
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/commanddi/CommandObjectDISpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/commanddi/CommandObjectDISpec.groovy
index 0762550402..368ef4df52 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/commanddi/CommandObjectDISpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/commanddi/CommandObjectDISpec.groovy
@@ -19,37 +19,25 @@
package functionaltests.commanddi
import io.micronaut.http.HttpRequest
-import io.micronaut.http.client.HttpClient
-import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for Command Objects with Dependency Injection.
* Tests the ability to inject Spring services into Grails command objects.
*/
@Integration
-class CommandObjectDISpec extends Specification {
-
- @Shared
- HttpClient client
-
- def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:$serverPort"))
- }
-
- def cleanupSpec() {
- client.close()
- }
+class CommandObjectDISpec extends Specification implements HttpClientSupport {
// ========== Basic Service Injection Tests ==========
def "service is properly injected into command object"() {
when: "calling the test endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/testServiceInjection'),
+ def response = httpClient.exchange(
+ '/commandDI/testServiceInjection',
Map
)
@@ -61,8 +49,8 @@ class CommandObjectDISpec extends Specification {
def "multiple services can be injected into single command object"() {
when: "calling the endpoint with order command"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/testMultipleServices'),
+ def response = httpClient.exchange(
+ '/commandDI/testMultipleServices',
Map
)
@@ -76,8 +64,8 @@ class CommandObjectDISpec extends Specification {
def "@Autowired annotation works for service injection"() {
when: "calling the endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/testAutowiredAnnotation'),
+ def response = httpClient.exchange(
+ '/commandDI/testAutowiredAnnotation',
Map
)
@@ -91,8 +79,8 @@ class CommandObjectDISpec extends Specification {
def "custom validation using injected service - valid username"() {
when: "registering with valid data"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/registerUser?username=johndoe&email=john@example.com&age=25'),
+ def response = httpClient.exchange(
+ '/commandDI/registerUser?username=johndoe&email=john@example.com&age=25',
Map
)
@@ -106,8 +94,8 @@ class CommandObjectDISpec extends Specification {
def "custom validation using injected service - reserved username rejected"() {
when: "registering with reserved username 'admin'"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/registerUser?username=admin&email=admin@example.com'),
+ def response = httpClient.exchange(
+ '/commandDI/registerUser?username=admin&email=admin@example.com',
Map
)
@@ -121,8 +109,8 @@ class CommandObjectDISpec extends Specification {
@Unroll
def "email domain validation - #email is #expectedResult"() {
when: "registering with specific email"
- def response = client.toBlocking().exchange(
- HttpRequest.GET("/commandDI/registerUser?username=testuser&email=${URLEncoder.encode(email, 'UTF-8')}"),
+ def response = httpClient.exchange(
+ "/commandDI/registerUser?username=testuser&email=${URLEncoder.encode(email, 'UTF-8')}",
Map
)
@@ -142,8 +130,8 @@ class CommandObjectDISpec extends Specification {
@Unroll
def "age validation - age #age is #expectedResult"() {
when: "registering with specific age"
- def response = client.toBlocking().exchange(
- HttpRequest.GET("/commandDI/registerUser?username=testuser&email=user@example.com&age=${age}"),
+ def response = httpClient.exchange(
+ "/commandDI/registerUser?username=testuser&email=user@example.com&age=${age}",
Map
)
@@ -166,8 +154,8 @@ class CommandObjectDISpec extends Specification {
def "phone number validation using service"() {
when: "registering with valid phone"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/registerUser?username=testuser&email=user@example.com&phone=555-123-4567'),
+ def response = httpClient.exchange(
+ '/commandDI/registerUser?username=testuser&email=user@example.com&phone=555-123-4567',
Map
)
@@ -178,8 +166,8 @@ class CommandObjectDISpec extends Specification {
def "phone number validation fails for invalid phone"() {
when: "registering with too short phone"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/registerUser?username=testuser&email=user@example.com&phone=123'),
+ def response = httpClient.exchange(
+ '/commandDI/registerUser?username=testuser&email=user@example.com&phone=123',
Map
)
@@ -193,8 +181,8 @@ class CommandObjectDISpec extends Specification {
@Unroll
def "discount calculation - quantity #quantity gives #expectedDiscount discount"() {
when: "calculating order"
- def response = client.toBlocking().exchange(
- HttpRequest.GET("/commandDI/calculateOrder?productName=Widget&quantity=${quantity}&unitPrice=10.00"),
+ def response = httpClient.exchange(
+ "/commandDI/calculateOrder?productName=Widget&quantity=${quantity}&unitPrice=10.00",
Map
)
@@ -216,8 +204,8 @@ class CommandObjectDISpec extends Specification {
def "total price calculation with discount"() {
when: "calculating order"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/calculateOrder?productName=Widget&quantity=100&unitPrice=10.00'),
+ def response = httpClient.exchange(
+ '/commandDI/calculateOrder?productName=Widget&quantity=100&unitPrice=10.00',
Map
)
@@ -228,8 +216,8 @@ class CommandObjectDISpec extends Specification {
def "price with tax calculation"() {
when: "calculating order"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/calculateOrder?productName=Premium+Widget&quantity=10&unitPrice=100.00'),
+ def response = httpClient.exchange(
+ '/commandDI/calculateOrder?productName=Premium+Widget&quantity=10&unitPrice=100.00',
Map
)
@@ -243,8 +231,8 @@ class CommandObjectDISpec extends Specification {
def "order validation with valid data"() {
when: "validating order"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/validateOrder?productName=Widget&quantity=5&unitPrice=25.00'),
+ def response = httpClient.exchange(
+ '/commandDI/validateOrder?productName=Widget&quantity=5&unitPrice=25.00',
Map
)
@@ -256,8 +244,8 @@ class CommandObjectDISpec extends Specification {
def "order validation fails for price out of range"() {
when: "validating order with excessive price"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/validateOrder?productName=Widget&quantity=5&unitPrice=99999.00'),
+ def response = httpClient.exchange(
+ '/commandDI/validateOrder?productName=Widget&quantity=5&unitPrice=99999.00',
Map
)
@@ -271,8 +259,8 @@ class CommandObjectDISpec extends Specification {
def "command object can invoke service method to send notification"() {
when: "sending order notification"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/sendOrderNotification?customerEmail=customer@example.com&orderId=ORD-12345&message=Thank+you+for+your+order!'),
+ def response = httpClient.exchange(
+ '/commandDI/sendOrderNotification?customerEmail=customer@example.com&orderId=ORD-12345&message=Thank+you+for+your+order!',
Map
)
@@ -287,8 +275,8 @@ class CommandObjectDISpec extends Specification {
def "command object is prototype scoped - fresh instance per request"() {
when: "making first request"
- def response1 = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/testPrototypeScope'),
+ def response1 = httpClient.exchange(
+ '/commandDI/testPrototypeScope',
Map
)
@@ -297,8 +285,8 @@ class CommandObjectDISpec extends Specification {
response1.body().counter == 2
when: "making second request"
- def response2 = client.toBlocking().exchange(
- HttpRequest.GET('/commandDI/testPrototypeScope'),
+ def response2 = httpClient.exchange(
+ '/commandDI/testPrototypeScope',
Map
)
@@ -312,7 +300,7 @@ class CommandObjectDISpec extends Specification {
def "service remains injected after validation"() {
when: "testing service after validation"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/commandDI/testServiceAfterValidation?username=admin&email=test@example.com'),
Map
)
@@ -327,7 +315,7 @@ class CommandObjectDISpec extends Specification {
def "missing optional service is handled gracefully"() {
when: "testing optional service"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/commandDI/testOptionalService?data=test'),
Map
)
@@ -342,7 +330,7 @@ class CommandObjectDISpec extends Specification {
def "validation can depend on service state"() {
when: "validating with positive price"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/commandDI/validateWithServiceState?price=100.00'),
Map
)
@@ -357,7 +345,7 @@ class CommandObjectDISpec extends Specification {
def "command object handles valid user request"() {
when: "validating user"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/commandDI/registerUser?username=validuser&email=user@example.com'),
Map
)
@@ -370,7 +358,7 @@ class CommandObjectDISpec extends Specification {
@Unroll
def "reserved username '#username' is rejected"() {
when: "registering with reserved username"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET("/commandDI/registerUser?username=${username}&email=test@example.com"),
Map
)
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/contentneg/ContentNegotiationSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/contentneg/ContentNegotiationSpec.groovy
index 307561404e..adf4bd6824 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/contentneg/ContentNegotiationSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/contentneg/ContentNegotiationSpec.groovy
@@ -23,13 +23,12 @@ import groovy.json.JsonSlurper
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
-import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
import spock.lang.Narrative
-import spock.lang.Shared
import spock.lang.Specification
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for Grails content negotiation features.
@@ -44,24 +43,13 @@ Grails provides content negotiation that allows the same controller action
to return different response formats (JSON, XML, HTML) based on the client's
Accept header or URL extension.
''')
-class ContentNegotiationSpec extends Specification {
-
- @Shared
- HttpClient client
-
- def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:$serverPort"))
- }
-
- def cleanupSpec() {
- client.close()
- }
+class ContentNegotiationSpec extends Specification implements HttpClientSupport {
// ========== Accept Header-Based Negotiation ==========
def "JSON response via Accept header application/json"() {
when: "requesting with Accept: application/json"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/index')
.accept(MediaType.APPLICATION_JSON),
String
@@ -79,7 +67,7 @@ class ContentNegotiationSpec extends Specification {
def "XML response via Accept header application/xml"() {
when: "requesting with Accept: application/xml"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/index')
.accept(MediaType.APPLICATION_XML),
String
@@ -95,7 +83,7 @@ class ContentNegotiationSpec extends Specification {
def "HTML response via Accept header text/html"() {
when: "requesting with Accept: text/html"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/index')
.accept(MediaType.TEXT_HTML),
String
@@ -114,7 +102,7 @@ class ContentNegotiationSpec extends Specification {
def "JSON response via .json extension"() {
when: "requesting URL with .json extension"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/index.json'),
String
)
@@ -129,7 +117,7 @@ class ContentNegotiationSpec extends Specification {
def "XML response via .xml extension"() {
when: "requesting URL with .xml extension"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/index.xml'),
String
)
@@ -145,7 +133,7 @@ class ContentNegotiationSpec extends Specification {
def "respond method returns JSON for Accept application/json"() {
when: "calling respond action with Accept: application/json"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/respond')
.accept(MediaType.APPLICATION_JSON),
String
@@ -163,7 +151,7 @@ class ContentNegotiationSpec extends Specification {
def "respond method returns XML for Accept application/xml"() {
when: "calling respond action with Accept: application/xml"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/respond')
.accept(MediaType.APPLICATION_XML),
String
@@ -178,7 +166,7 @@ class ContentNegotiationSpec extends Specification {
def "list action returns JSON array"() {
when: "requesting list with Accept: application/json"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/list')
.accept(MediaType.APPLICATION_JSON),
String
@@ -197,7 +185,7 @@ class ContentNegotiationSpec extends Specification {
def "list action returns XML for XML accept"() {
when: "requesting list with Accept: application/xml"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/list')
.accept(MediaType.APPLICATION_XML),
String
@@ -212,7 +200,7 @@ class ContentNegotiationSpec extends Specification {
def "explicit content type overrides negotiation"() {
when: "calling action with explicit content type"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/explicitContentType'),
String
)
@@ -230,7 +218,7 @@ class ContentNegotiationSpec extends Specification {
def "error response in JSON format"() {
when: "requesting error action with Accept: application/json"
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.GET('/contentNegotiation/error')
.accept(MediaType.APPLICATION_JSON),
String
@@ -249,7 +237,7 @@ class ContentNegotiationSpec extends Specification {
def "error response in XML format"() {
when: "requesting error action with Accept: application/xml"
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.GET('/contentNegotiation/error')
.accept(MediaType.APPLICATION_XML),
String
@@ -267,7 +255,7 @@ class ContentNegotiationSpec extends Specification {
def "format parameter can specify JSON"() {
when: "requesting with format=json parameter"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/formatParam?format=json'),
String
)
@@ -283,7 +271,7 @@ class ContentNegotiationSpec extends Specification {
def "format parameter can specify XML"() {
when: "requesting with format=xml parameter"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/formatParam?format=xml'),
String
)
@@ -297,7 +285,7 @@ class ContentNegotiationSpec extends Specification {
def "status by format returns correct status for JSON"() {
when: "requesting statusByFormat with Accept: application/json"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/statusByFormat')
.accept(MediaType.APPLICATION_JSON),
String
@@ -315,7 +303,7 @@ class ContentNegotiationSpec extends Specification {
def "multiAccept action handles Accept header"() {
when: "requesting with complex Accept header"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/multiAccept')
.accept(MediaType.APPLICATION_JSON),
String
@@ -333,7 +321,7 @@ class ContentNegotiationSpec extends Specification {
def "formatParam falls back to default for unknown format"() {
when: "requesting with unknown format"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/formatParam?format=unknown'),
String
)
@@ -351,7 +339,7 @@ class ContentNegotiationSpec extends Specification {
def "JSON with charset in Accept header"() {
when: "requesting with Accept including charset"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/respond')
.header('Accept', 'application/json; charset=utf-8'),
String
@@ -366,7 +354,7 @@ class ContentNegotiationSpec extends Specification {
def "respond action with .json extension"() {
when: "requesting respond with .json extension"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/respond.json'),
String
)
@@ -381,7 +369,7 @@ class ContentNegotiationSpec extends Specification {
def "list action with .json extension"() {
when: "requesting list with .json extension"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/list.json'),
String
)
@@ -399,7 +387,7 @@ class ContentNegotiationSpec extends Specification {
def "custom JSON rendering produces valid output"() {
when: "requesting custom JSON action"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/contentNegotiation/customJson'),
String
)
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/cors/CorsAdvancedSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/cors/CorsAdvancedSpec.groovy
index 951db909f2..ce0e6626e6 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/cors/CorsAdvancedSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/cors/CorsAdvancedSpec.groovy
@@ -23,12 +23,11 @@ import groovy.json.JsonSlurper
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
-import io.micronaut.http.client.HttpClient
-import spock.lang.Shared
import spock.lang.Specification
import grails.gorm.transactions.Rollback
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for CORS (Cross-Origin Resource Sharing) functionality.
@@ -38,24 +37,13 @@ import grails.testing.mixin.integration.Integration
*/
@Rollback
@Integration
-class CorsAdvancedSpec extends Specification {
-
- @Shared
- HttpClient client
-
- def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:${serverPort}"))
- }
-
- def cleanupSpec() {
- client.close()
- }
+class CorsAdvancedSpec extends Specification implements HttpClientSupport {
// ========== Basic CORS Header Tests ==========
def "GET request to CORS-enabled endpoint includes CORS headers"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/cors')
.header('Origin', 'http://example.com'),
String
@@ -69,7 +57,7 @@ class CorsAdvancedSpec extends Specification {
def "OPTIONS preflight request returns appropriate headers"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.OPTIONS('/api/cors/data')
.header('Origin', 'http://example.com')
.header('Access-Control-Request-Method', 'GET'),
@@ -84,7 +72,7 @@ class CorsAdvancedSpec extends Specification {
def "preflight request for POST method succeeds"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.OPTIONS('/api/cors/items')
.header('Origin', 'http://example.com')
.header('Access-Control-Request-Method', 'POST')
@@ -100,7 +88,7 @@ class CorsAdvancedSpec extends Specification {
def "preflight request for PUT method succeeds"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.OPTIONS('/api/cors/items/1')
.header('Origin', 'http://example.com')
.header('Access-Control-Request-Method', 'PUT')
@@ -114,7 +102,7 @@ class CorsAdvancedSpec extends Specification {
def "preflight request for DELETE method succeeds"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.OPTIONS('/api/cors/items/1')
.header('Origin', 'http://example.com')
.header('Access-Control-Request-Method', 'DELETE'),
@@ -129,7 +117,7 @@ class CorsAdvancedSpec extends Specification {
def "GET request to CORS endpoint returns data"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/cors/data')
.header('Origin', 'http://example.com'),
String
@@ -144,7 +132,7 @@ class CorsAdvancedSpec extends Specification {
def "POST request with CORS headers succeeds"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/api/cors/items', '{"name":"New Item"}')
.header('Origin', 'http://example.com')
.contentType(MediaType.APPLICATION_JSON),
@@ -160,7 +148,7 @@ class CorsAdvancedSpec extends Specification {
def "PUT request with CORS headers succeeds"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.PUT('/api/cors/items/42', '{"name":"Updated Item"}')
.header('Origin', 'http://example.com')
.contentType(MediaType.APPLICATION_JSON),
@@ -177,7 +165,7 @@ class CorsAdvancedSpec extends Specification {
def "DELETE request with CORS headers succeeds"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.DELETE('/api/cors/items/99')
.header('Origin', 'http://example.com'),
String
@@ -195,7 +183,7 @@ class CorsAdvancedSpec extends Specification {
def "response with custom headers includes CORS headers"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/cors/custom-headers')
.header('Origin', 'http://example.com'),
String
@@ -210,7 +198,7 @@ class CorsAdvancedSpec extends Specification {
def "echo origin endpoint returns received origin"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/cors/echo-origin')
.header('Origin', 'http://my-app.example.com'),
String
@@ -226,7 +214,7 @@ class CorsAdvancedSpec extends Specification {
def "authenticated endpoint receives authorization header"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/cors/authenticated')
.header('Origin', 'http://example.com')
.header('Authorization', 'Bearer test-token-123'),
@@ -242,7 +230,7 @@ class CorsAdvancedSpec extends Specification {
def "authenticated endpoint without credentials returns unauthenticated"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/cors/authenticated')
.header('Origin', 'http://example.com'),
String
@@ -259,7 +247,7 @@ class CorsAdvancedSpec extends Specification {
def "request from localhost origin succeeds"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/cors')
.header('Origin', 'http://localhost:3000'),
String
@@ -273,7 +261,7 @@ class CorsAdvancedSpec extends Specification {
def "request from HTTPS origin succeeds"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/cors')
.header('Origin', 'https://secure.example.com'),
String
@@ -287,7 +275,7 @@ class CorsAdvancedSpec extends Specification {
def "request with port in origin succeeds"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/cors')
.header('Origin', 'http://example.com:8080'),
String
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/errorhandling/ErrorHandlingSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/errorhandling/ErrorHandlingSpec.groovy
index 79ff98bea7..70c1ca2b70 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/errorhandling/ErrorHandlingSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/errorhandling/ErrorHandlingSpec.groovy
@@ -21,16 +21,14 @@ package functionaltests.errorhandling
import groovy.json.JsonSlurper
import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
-import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
import grails.gorm.transactions.Rollback
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for error handling patterns in Grails controllers.
@@ -39,25 +37,14 @@ import grails.testing.mixin.integration.Integration
*/
@Rollback
@Integration
-class ErrorHandlingSpec extends Specification {
-
- @Shared
- HttpClient client
-
- def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:${serverPort}"))
- }
-
- def cleanupSpec() {
- client.close()
- }
+class ErrorHandlingSpec extends Specification implements HttpClientSupport {
// ========== HTTP Status Code Tests ==========
@Unroll
def "render #statusMsg status"(String action, HttpStatus status, String statusMsg) {
when:
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.GET("/errorHandlingTest/$action"),
String
)
@@ -86,7 +73,7 @@ class ErrorHandlingSpec extends Specification {
@Unroll
def "JSON #statusCode.code error response #assertion"(String action, HttpStatus statusCode, String assertion) {
when:
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.GET("/errorHandlingTest/$action")
.accept('application/json'),
String
@@ -109,7 +96,7 @@ class ErrorHandlingSpec extends Specification {
@Unroll
def "conditional error returns #status.code when condition is #condition"(String condition, HttpStatus status) {
when:
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.GET("/errorHandlingTest/conditionalError?condition=$condition")
.accept('application/json'),
String
@@ -128,7 +115,7 @@ class ErrorHandlingSpec extends Specification {
def "conditional error returns success for unknown condition"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/errorHandlingTest/conditionalError?condition=normal')
.accept('application/json'),
String
@@ -147,7 +134,7 @@ class ErrorHandlingSpec extends Specification {
def "rate limit error includes appropriate headers"() {
when:
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.GET('/errorHandlingTest/errorWithHeaders').accept('application/json'),
String
)
@@ -166,7 +153,7 @@ class ErrorHandlingSpec extends Specification {
def "not found error includes suggestion header"() {
when:
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.GET('/errorHandlingTest/notFoundWithHints').accept('application/json'),
String
)
@@ -183,7 +170,7 @@ class ErrorHandlingSpec extends Specification {
def "success endpoint returns 200 OK"() {
when:
- HttpResponse<String> response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/errorHandlingTest/success').accept('application/json'),
String
)
@@ -199,7 +186,7 @@ class ErrorHandlingSpec extends Specification {
def "success with data returns structured response"() {
when:
- HttpResponse<String> response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/errorHandlingTest/successWithData').accept('application/json'),
String
)
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/fileupload/FileUploadSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/fileupload/FileUploadSpec.groovy
index a882e31214..ea2080ff67 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/fileupload/FileUploadSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/fileupload/FileUploadSpec.groovy
@@ -23,14 +23,13 @@ import groovy.json.JsonSlurper
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
-import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
import io.micronaut.http.client.multipart.MultipartBody
-import spock.lang.Shared
import spock.lang.Specification
import grails.gorm.transactions.Rollback
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for file upload functionality in Grails.
@@ -39,18 +38,7 @@ import grails.testing.mixin.integration.Integration
*/
@Rollback
@Integration
-class FileUploadSpec extends Specification {
-
- @Shared
- HttpClient client
-
- def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:${serverPort}"))
- }
-
- def cleanupSpec() {
- client.close()
- }
+class FileUploadSpec extends Specification implements HttpClientSupport {
// ========== Single File Upload Tests ==========
@@ -62,7 +50,7 @@ class FileUploadSpec extends Specification {
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadSingle', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -83,7 +71,7 @@ class FileUploadSpec extends Specification {
.build()
when:
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadSingle', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -104,7 +92,7 @@ class FileUploadSpec extends Specification {
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadWithMetadata', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -129,7 +117,7 @@ class FileUploadSpec extends Specification {
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadMultiple', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -157,7 +145,7 @@ This is a longer line with more words'''
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadTextFile', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -180,7 +168,7 @@ This is a longer line with more words'''
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadAndEcho', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -202,7 +190,7 @@ This is a longer line with more words'''
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadWithValidation', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -222,7 +210,7 @@ This is a longer line with more words'''
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadWithExtensionValidation', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -244,7 +232,7 @@ This is a longer line with more words'''
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadWithExtensionValidation', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -266,7 +254,7 @@ This is a longer line with more words'''
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/getFileInfo', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -289,7 +277,7 @@ This is a longer line with more words'''
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/getFileInfo', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -312,7 +300,7 @@ This is a longer line with more words'''
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadViaParams', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -336,7 +324,7 @@ This is a longer line with more words'''
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadSingle', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -357,7 +345,7 @@ This is a longer line with more words'''
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadAndEcho', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
@@ -379,7 +367,7 @@ This is a longer line with more words'''
.build()
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/fileUploadTest/uploadAndEcho', body)
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE),
String
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/flow/FlashChainForwardSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/flow/FlashChainForwardSpec.groovy
index e59b151928..33dd88843e 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/flow/FlashChainForwardSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/flow/FlashChainForwardSpec.groovy
@@ -19,15 +19,15 @@
package functionaltests.flow
import groovy.json.JsonSlurper
+
import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpStatus
-import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.DefaultHttpClientConfiguration
-import io.micronaut.http.client.exceptions.HttpClientResponseException
+import io.micronaut.http.client.HttpClient
import spock.lang.Shared
import spock.lang.Specification
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for controller flow features:
@@ -40,28 +40,19 @@ import grails.testing.mixin.integration.Integration
* flash scope and chain model which rely on HTTP session state.
*/
@Integration
-class FlashChainForwardSpec extends Specification {
-
- @Shared
- HttpClient client
+class FlashChainForwardSpec extends Specification implements HttpClientSupport {
@Shared
HttpClient noRedirectClient
def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:$serverPort"))
if (!noRedirectClient) {
- def config = new DefaultHttpClientConfiguration()
- config.setFollowRedirects(false)
- noRedirectClient = HttpClient.create(new URL("http://localhost:$serverPort"), config)
+ noRedirectClient = newHttpClient(new DefaultHttpClientConfiguration().tap {
+ followRedirects = false
+ })
}
}
-
- def cleanupSpec() {
- client?.close()
- noRedirectClient?.close()
- }
-
+
/**
* Helper to extract session cookie from Set-Cookie header.
*/
@@ -85,7 +76,7 @@ class FlashChainForwardSpec extends Specification {
if (!location) {
// Not a redirect, parse body
- return new JsonSlurper().parseText(response1.body())
+ return new JsonSlurper().parseText(response1.body()) as Map
}
// Follow redirect with session cookie
@@ -98,8 +89,8 @@ class FlashChainForwardSpec extends Specification {
request2 = request2.header('Cookie', sessionCookie)
}
- def response2 = client.toBlocking().exchange(request2, String)
- new JsonSlurper().parseText(response2.body())
+ def response2 = httpClient.exchange(request2, String)
+ new JsonSlurper().parseText(response2.body()) as Map
}
/**
@@ -128,7 +119,7 @@ class FlashChainForwardSpec extends Specification {
def location = response.header('Location')
if (!location) {
// No more redirects, return parsed body
- return new JsonSlurper().parseText(response.body())
+ return new JsonSlurper().parseText(response.body() as String) as Map
}
// Follow to next location
@@ -138,7 +129,7 @@ class FlashChainForwardSpec extends Specification {
redirectCount++
}
- throw new RuntimeException("Too many redirects following chain")
+ throw new RuntimeException('Too many redirects following chain')
}
// ========== Flash Scope Tests ==========
@@ -166,8 +157,8 @@ class FlashChainForwardSpec extends Specification {
def "test flash.now for same-request values"() {
when: "using flash.now"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/flow/flashNow'),
+ def response = httpClient.exchange(
+ '/flow/flashNow',
Map
)
@@ -190,7 +181,7 @@ class FlashChainForwardSpec extends Specification {
if (sessionCookie) {
request2 = request2.header('Cookie', sessionCookie)
}
- def response2 = client.toBlocking().exchange(request2, String)
+ def response2 = httpClient.exchange(request2, String)
def json2 = new JsonSlurper().parseText(response2.body())
and: "third request tries to read again with same session"
@@ -198,7 +189,7 @@ class FlashChainForwardSpec extends Specification {
if (sessionCookie) {
request3 = request3.header('Cookie', sessionCookie)
}
- def response3 = client.toBlocking().exchange(request3, String)
+ def response3 = httpClient.exchange(request3, String)
def json3 = new JsonSlurper().parseText(response3.body())
then: "flash available in second request"
@@ -244,8 +235,8 @@ class FlashChainForwardSpec extends Specification {
def "test forward keeps same request"() {
when: "forwarding to another action"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/flow/forwardToAction'),
+ def response = httpClient.exchange(
+ '/flow/forwardToAction',
Map
)
@@ -257,8 +248,8 @@ class FlashChainForwardSpec extends Specification {
def "test forward with params"() {
when: "forwarding with additional params"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/flow/forwardWithParams?id=original'),
+ def response = httpClient.exchange(
+ '/flow/forwardWithParams?id=original',
Map
)
@@ -270,7 +261,7 @@ class FlashChainForwardSpec extends Specification {
def "test forward to different controller"() {
when: "forwarding to another controller"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/flow/forwardToOtherController'),
Map
)
@@ -285,7 +276,7 @@ class FlashChainForwardSpec extends Specification {
def "test redirect preserves all params"() {
when: "redirecting with params"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/flow/redirectWithAllParams?foo=bar&num=42'),
Map
)
@@ -298,7 +289,7 @@ class FlashChainForwardSpec extends Specification {
def "test redirect to uri"() {
when: "redirecting to specific URI"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/flow/redirectToUri'),
Map
)
@@ -310,7 +301,7 @@ class FlashChainForwardSpec extends Specification {
def "test redirect reaches target"() {
when: "basic redirect"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/flow/permanentRedirect'),
Map
)
@@ -324,7 +315,7 @@ class FlashChainForwardSpec extends Specification {
def "test chain model is empty when not chained"() {
when: "calling chain target directly"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/flow/chainThird'),
Map
)
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/i18n/InternationalizationSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/i18n/InternationalizationSpec.groovy
index 1c7a33e142..8cc05478ff 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/i18n/InternationalizationSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/i18n/InternationalizationSpec.groovy
@@ -19,11 +19,10 @@
package functionaltests.i18n
import io.micronaut.http.HttpRequest
-import io.micronaut.http.client.HttpClient
-import spock.lang.Shared
import spock.lang.Specification
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Comprehensive integration tests for internationalization (i18n) features.
@@ -39,24 +38,13 @@ import grails.testing.mixin.integration.Integration
* - Accept-Language header handling
*/
@Integration
-class InternationalizationSpec extends Specification {
-
- @Shared
- HttpClient client
-
- def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:$serverPort"))
- }
-
- def cleanupSpec() {
- client.close()
- }
+class InternationalizationSpec extends Specification implements HttpClientSupport {
// ========== Basic Message Resolution Tests ==========
def "test simple message in English"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getMessage?code=app.welcome&lang=en'),
Map
)
@@ -70,7 +58,7 @@ class InternationalizationSpec extends Specification {
def "test simple message in German"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getMessage?code=app.welcome&lang=de'),
Map
)
@@ -84,7 +72,7 @@ class InternationalizationSpec extends Specification {
def "test simple message in French"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getMessage?code=app.welcome&lang=fr'),
Map
)
@@ -100,7 +88,7 @@ class InternationalizationSpec extends Specification {
def "test message with argument in English"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getMessageWithArgs?code=app.greeting&arg=John&lang=en'),
Map
)
@@ -112,7 +100,7 @@ class InternationalizationSpec extends Specification {
def "test message with argument in German"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getMessageWithArgs?code=app.greeting&arg=Johann&lang=de'),
Map
)
@@ -124,7 +112,7 @@ class InternationalizationSpec extends Specification {
def "test farewell message with argument"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getMessageWithArgs?code=app.farewell&arg=Alice&lang=en'),
Map
)
@@ -138,7 +126,7 @@ class InternationalizationSpec extends Specification {
def "test choice format - zero items in English"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getChoiceMessage?count=0&lang=en'),
Map
)
@@ -151,7 +139,7 @@ class InternationalizationSpec extends Specification {
def "test choice format - one item in English"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getChoiceMessage?count=1&lang=en'),
Map
)
@@ -164,7 +152,7 @@ class InternationalizationSpec extends Specification {
def "test choice format - multiple items in English"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getChoiceMessage?count=5&lang=en'),
Map
)
@@ -177,7 +165,7 @@ class InternationalizationSpec extends Specification {
def "test choice format in German"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getChoiceMessage?count=0&lang=de'),
Map
)
@@ -189,7 +177,7 @@ class InternationalizationSpec extends Specification {
def "test choice format - one item in German"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getChoiceMessage?count=1&lang=de'),
Map
)
@@ -203,7 +191,7 @@ class InternationalizationSpec extends Specification {
def "test date formatting in English"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getDateMessage?lang=en'),
Map
)
@@ -217,7 +205,7 @@ class InternationalizationSpec extends Specification {
def "test date formatting in German"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getDateMessage?lang=de'),
Map
)
@@ -232,7 +220,7 @@ class InternationalizationSpec extends Specification {
def "test currency formatting in English US"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getCurrencyMessage?amount=1234.56&lang=en_US'),
Map
)
@@ -244,7 +232,7 @@ class InternationalizationSpec extends Specification {
def "test currency formatting in German"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getCurrencyMessage?amount=1234.56&lang=de_DE'),
Map
)
@@ -259,7 +247,7 @@ class InternationalizationSpec extends Specification {
def "test percentage formatting in English"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getPercentMessage?value=0.75&lang=en'),
Map
)
@@ -274,7 +262,7 @@ class InternationalizationSpec extends Specification {
def "test default message for non-existent code"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getMessageWithDefault?code=non.existent.key&defaultMsg=Fallback+Message&lang=en'),
Map
)
@@ -288,7 +276,7 @@ class InternationalizationSpec extends Specification {
def "test validation messages in English"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getValidationMessages?lang=en'),
Map
)
@@ -303,7 +291,7 @@ class InternationalizationSpec extends Specification {
def "test validation messages in German"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getValidationMessages?lang=de'),
Map
)
@@ -316,7 +304,7 @@ class InternationalizationSpec extends Specification {
def "test validation messages in French"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getValidationMessages?lang=fr'),
Map
)
@@ -331,7 +319,7 @@ class InternationalizationSpec extends Specification {
def "test multiple messages at once in English"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getMultipleMessages?lang=en'),
Map
)
@@ -345,7 +333,7 @@ class InternationalizationSpec extends Specification {
def "test multiple messages at once in German"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getMultipleMessages?lang=de'),
Map
)
@@ -361,7 +349,7 @@ class InternationalizationSpec extends Specification {
def "test current locale information"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getCurrentLocale?lang=de_DE'),
Map
)
@@ -376,7 +364,7 @@ class InternationalizationSpec extends Specification {
def "test locale from Accept-Language header - German"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getLocaleFromHeader')
.header('Accept-Language', 'de-DE'),
Map
@@ -390,7 +378,7 @@ class InternationalizationSpec extends Specification {
def "test locale from Accept-Language header - French"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getLocaleFromHeader')
.header('Accept-Language', 'fr-FR'),
Map
@@ -405,7 +393,7 @@ class InternationalizationSpec extends Specification {
def "test controller message method"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/useControllerMessage?code=app.welcome&lang=en'),
Map
)
@@ -419,7 +407,7 @@ class InternationalizationSpec extends Specification {
def "test fallback to default locale when unsupported locale requested"() {
when: "requesting a locale that doesn't have translations"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getMessage?code=app.welcome&lang=xyz'),
Map
)
@@ -432,7 +420,7 @@ class InternationalizationSpec extends Specification {
def "test message with special characters"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/i18nTest/getMessageWithArgs?code=app.greeting&arg=%C3%A9l%C3%A8ve&lang=en'),
Map
)
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/interceptors/InterceptorAdvancedMatchingSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/interceptors/InterceptorAdvancedMatchingSpec.groovy
index fea70005b0..7ce23d45f2 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/interceptors/InterceptorAdvancedMatchingSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/interceptors/InterceptorAdvancedMatchingSpec.groovy
@@ -19,11 +19,10 @@
package functionaltests.interceptors
import io.micronaut.http.HttpRequest
-import io.micronaut.http.client.HttpClient
-import spock.lang.Shared
import spock.lang.Specification
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for advanced interceptor matching patterns.
@@ -36,30 +35,22 @@ import grails.testing.mixin.integration.Integration
* - Combined matching criteria
*/
@Integration
-class InterceptorAdvancedMatchingSpec extends Specification {
-
- @Shared
- HttpClient client
+class InterceptorAdvancedMatchingSpec extends Specification implements HttpClientSupport {
def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:$serverPort"))
// Reset interceptor tracking before each test
- client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/reset'),
+ httpClient.exchange(
+ '/api/advancedMatching/reset',
Map
)
}
- def cleanupSpec() {
- client.close()
- }
-
// ========== Namespace Matching Tests ==========
def "test namespace interceptor matches controller in api namespace"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/index'),
+ def response = httpClient.exchange(
+ '/api/advancedMatching/index',
Map
)
@@ -71,8 +62,8 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test namespace interceptor matches all actions in namespace"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/list'),
+ def response = httpClient.exchange(
+ '/api/advancedMatching/list',
Map
)
@@ -86,7 +77,7 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test method interceptor matches POST requests"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/api/advancedMatching/save', [:]),
Map
)
@@ -99,8 +90,8 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test method interceptor does not match GET requests"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/list'),
+ def response = httpClient.exchange(
+ '/api/advancedMatching/list',
Map
)
@@ -111,7 +102,7 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test method interceptor matches PUT requests as POST variant"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.PUT('/api/advancedMatching/update', [:]),
Map
)
@@ -126,8 +117,8 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test excludes interceptor does not match excluded index action"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/index'),
+ def response = httpClient.exchange(
+ '/api/advancedMatching/index',
Map
)
@@ -139,8 +130,8 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test excludes interceptor matches non-excluded actions"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/list'),
+ def response = httpClient.exchange(
+ '/api/advancedMatching/list',
Map
)
@@ -152,8 +143,8 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test excludes interceptor matches show action"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/show/123'),
+ def response = httpClient.exchange(
+ '/api/advancedMatching/show/123',
Map
)
@@ -167,8 +158,8 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test multiple rules interceptor matches show action"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/show/1'),
+ def response = httpClient.exchange(
+ '/api/advancedMatching/show/1',
Map
)
@@ -180,8 +171,8 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test multiple rules interceptor matches list action"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/list'),
+ def response = httpClient.exchange(
+ '/api/advancedMatching/list',
Map
)
@@ -193,8 +184,8 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test multiple rules interceptor does not match create action"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/create'),
+ def response = httpClient.exchange(
+ '/api/advancedMatching/create',
Map
)
@@ -208,8 +199,8 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test multiple interceptors can match same request"() {
when: "accessing list action in api namespace"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/list'),
+ def response = httpClient.exchange(
+ '/api/advancedMatching/list',
Map
)
@@ -222,7 +213,7 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test POST to save triggers namespace and method interceptors"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/api/advancedMatching/save', [:]),
Map
)
@@ -238,8 +229,8 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test index action only matches namespace interceptor"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/index'),
+ def response = httpClient.exchange(
+ '/api/advancedMatching/index',
Map
)
@@ -255,8 +246,8 @@ class InterceptorAdvancedMatchingSpec extends Specification {
def "test interceptors execute in defined order"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/api/advancedMatching/list'),
+ def response = httpClient.exchange(
+ '/api/advancedMatching/list',
Map
)
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/interceptors/InterceptorOrderingSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/interceptors/InterceptorOrderingSpec.groovy
index b02c82528b..4344f18922 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/interceptors/InterceptorOrderingSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/interceptors/InterceptorOrderingSpec.groovy
@@ -19,11 +19,10 @@
package functionaltests.interceptors
import io.micronaut.http.HttpRequest
-import io.micronaut.http.client.HttpClient
-import spock.lang.Shared
import spock.lang.Specification
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Comprehensive integration tests for Grails interceptor functionality.
@@ -38,30 +37,22 @@ import grails.testing.mixin.integration.Integration
* - Timing/performance tracking
*/
@Integration
-class InterceptorOrderingSpec extends Specification {
-
- @Shared
- HttpClient client
+class InterceptorOrderingSpec extends Specification implements HttpClientSupport {
def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:$serverPort"))
// Reset execution order before each test
- client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/resetOrder'),
+ httpClient.exchange(
+ '/interceptorTest/resetOrder',
Map
)
}
- def cleanupSpec() {
- client.close()
- }
-
// ========== Interceptor Ordering Tests ==========
def "test interceptors execute in order by 'order' property"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/testOrder'),
+ def response = httpClient.exchange(
+ '/interceptorTest/testOrder',
Map
)
@@ -88,8 +79,8 @@ class InterceptorOrderingSpec extends Specification {
def "test before interceptors run before controller action"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/index'),
+ def response = httpClient.exchange(
+ '/interceptorTest/index',
Map
)
@@ -105,8 +96,8 @@ class InterceptorOrderingSpec extends Specification {
def "test after interceptors run after controller action"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/index'),
+ def response = httpClient.exchange(
+ '/interceptorTest/index',
Map
)
@@ -124,8 +115,8 @@ class InterceptorOrderingSpec extends Specification {
def "test interceptor can block request by returning false"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/blocked?block=true&reason=testing'),
+ def response = httpClient.exchange(
+ '/interceptorTest/blocked?block=true&reason=testing',
Map
)
@@ -138,8 +129,8 @@ class InterceptorOrderingSpec extends Specification {
def "test interceptor allows request when returning true"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/blocked?block=false'),
+ def response = httpClient.exchange(
+ '/interceptorTest/blocked?block=false',
Map
)
@@ -153,8 +144,8 @@ class InterceptorOrderingSpec extends Specification {
def "test interceptor can set request attributes"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/checkAttributes'),
+ def response = httpClient.exchange(
+ '/interceptorTest/checkAttributes',
Map
)
@@ -167,8 +158,8 @@ class InterceptorOrderingSpec extends Specification {
def "test interceptor can set response headers"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/checkAttributes'),
+ def response = httpClient.exchange(
+ '/interceptorTest/checkAttributes',
Map
)
@@ -181,8 +172,8 @@ class InterceptorOrderingSpec extends Specification {
def "test interceptor can set session attributes"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/checkSession'),
+ def response = httpClient.exchange(
+ '/interceptorTest/checkSession',
Map
)
@@ -196,15 +187,15 @@ class InterceptorOrderingSpec extends Specification {
def "test interceptor conditional matching - matched"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/conditionalAction?match=yes'),
+ def response = httpClient.exchange(
+ '/interceptorTest/conditionalAction?match=yes',
Map
)
then:
response.status.code == 200
- def orderResponse = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/getOrder'),
+ def orderResponse = httpClient.exchange(
+ '/interceptorTest/getOrder',
Map
)
orderResponse.body().executionOrder.contains('conditional:before:matched')
@@ -212,15 +203,15 @@ class InterceptorOrderingSpec extends Specification {
def "test interceptor conditional matching - not matched"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/conditionalAction?match=no'),
+ def response = httpClient.exchange(
+ '/interceptorTest/conditionalAction?match=no',
Map
)
then:
response.status.code == 200
- def orderResponse = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/getOrder'),
+ def orderResponse = httpClient.exchange(
+ '/interceptorTest/getOrder',
Map
)
orderResponse.body().executionOrder.contains('conditional:before:notmatched')
@@ -230,8 +221,8 @@ class InterceptorOrderingSpec extends Specification {
def "test timing interceptor tracks request duration"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/slowAction?delay=50'),
+ def response = httpClient.exchange(
+ '/interceptorTest/slowAction?delay=50',
Map
)
@@ -243,8 +234,8 @@ class InterceptorOrderingSpec extends Specification {
// Check that timing interceptor's before phase ran and recorded start time
// Note: We can only reliably verify before() since after() runs after response is committed
// and its execution order entry may not be visible due to async timing
- def orderResponse = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/getOrder'),
+ def orderResponse = httpClient.exchange(
+ '/interceptorTest/getOrder',
Map
)
orderResponse.body().executionOrder.any { it.startsWith('timing:before') }
@@ -254,15 +245,15 @@ class InterceptorOrderingSpec extends Specification {
def "test interceptors work correctly across multiple requests"() {
when: "make multiple requests"
- def response1 = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/index'),
+ def response1 = httpClient.exchange(
+ '/interceptorTest/index',
Map
)
// Reset again for clean second request
- client.toBlocking().exchange(HttpRequest.GET('/interceptorTest/resetOrder'), Map)
- def response2 = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/index'),
+ httpClient.retrieve('/interceptorTest/resetOrder')
+ def response2 = httpClient.exchange(
+ '/interceptorTest/index',
Map
)
@@ -279,8 +270,8 @@ class InterceptorOrderingSpec extends Specification {
def "test interceptors work with data actions"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/dataAction?data=testValue'),
+ def response = httpClient.exchange(
+ '/interceptorTest/dataAction?data=testValue',
Map
)
@@ -293,8 +284,8 @@ class InterceptorOrderingSpec extends Specification {
def "test complete before-controller-after sequence"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/interceptorTest/index'),
+ def response = httpClient.exchange(
+ '/interceptorTest/index',
Map
)
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/requestresponse/RequestResponseSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/requestresponse/RequestResponseSpec.groovy
index d2c44d6c78..41b8a914c7 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/requestresponse/RequestResponseSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/requestresponse/RequestResponseSpec.groovy
@@ -21,32 +21,19 @@ package functionaltests.requestresponse
import groovy.json.JsonSlurper
import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import io.micronaut.http.client.HttpClient
import io.micronaut.http.cookie.Cookie
-import spock.lang.Shared
import spock.lang.Specification
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for request/response handling patterns including
* headers, cookies, session management, and request attributes.
*/
@Integration
-class RequestResponseSpec extends Specification {
-
- @Shared
- HttpClient client
-
- def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:${serverPort}"))
- }
-
- def cleanupSpec() {
- client.close()
- }
+class RequestResponseSpec extends Specification implements HttpClientSupport {
/**
* Helper method to find header value case-insensitively.
@@ -61,7 +48,7 @@ class RequestResponseSpec extends Specification {
def "echo request headers returns all headers"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/requestResponseTest/echoHeaders')
.header('X-Custom-Header', 'TestValue')
.header('X-Another-Header', 'AnotherValue'),
@@ -77,7 +64,7 @@ class RequestResponseSpec extends Specification {
def "get specific header returns correct value"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/requestResponseTest/getSpecificHeader?headerName=X-Test-Header')
.header('X-Test-Header', 'MyTestValue'),
String
@@ -92,7 +79,7 @@ class RequestResponseSpec extends Specification {
def "check accept header detects JSON accept type"() {
when:
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/requestResponseTest/checkAcceptHeader')
.accept('application/json'),
String
@@ -108,8 +95,8 @@ class RequestResponseSpec extends Specification {
def "set custom headers returns headers in response"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/setCustomHeaders'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/setCustomHeaders',
String
)
@@ -122,8 +109,8 @@ class RequestResponseSpec extends Specification {
def "set cache headers configures caching properly"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/setCacheHeaders'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/setCacheHeaders',
String
)
@@ -136,8 +123,8 @@ class RequestResponseSpec extends Specification {
def "set no-cache headers prevents caching"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/setNoCacheHeaders'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/setNoCacheHeaders',
String
)
@@ -150,8 +137,8 @@ class RequestResponseSpec extends Specification {
def "set content disposition for file download"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/setContentDisposition'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/setContentDisposition',
String
)
@@ -162,8 +149,8 @@ class RequestResponseSpec extends Specification {
def "set multiple custom headers returns all headers"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/setMultipleCustomHeaders'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/setMultipleCustomHeaders',
String
)
@@ -178,8 +165,8 @@ class RequestResponseSpec extends Specification {
def "set cookie returns Set-Cookie header"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/setCookie?name=myCookie&value=myValue'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/setCookie?name=myCookie&value=myValue',
String
)
def json = new JsonSlurper().parseText(response.body())
@@ -194,8 +181,8 @@ class RequestResponseSpec extends Specification {
def "set multiple cookies returns multiple Set-Cookie headers"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/setMultipleCookies'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/setMultipleCookies',
String
)
def json = new JsonSlurper().parseText(response.body())
@@ -211,7 +198,7 @@ class RequestResponseSpec extends Specification {
def request = HttpRequest.GET('/requestResponseTest/getCookies')
.cookie(Cookie.of('testCookie1', 'value1'))
.cookie(Cookie.of('testCookie2', 'value2'))
- HttpResponse<String> response = client.toBlocking().exchange(request, String)
+ def response = httpClient.exchange(request, String)
def json = new JsonSlurper().parseText(response.body())
then:
@@ -224,7 +211,7 @@ class RequestResponseSpec extends Specification {
when:
def request = HttpRequest.GET('/requestResponseTest/getSpecificCookie?name=myCookie')
.cookie(Cookie.of('myCookie', 'cookieValue'))
- HttpResponse<String> response = client.toBlocking().exchange(request, String)
+ def response = httpClient.exchange(request, String)
def json = new JsonSlurper().parseText(response.body())
then:
@@ -236,8 +223,8 @@ class RequestResponseSpec extends Specification {
def "delete cookie sets max-age to 0"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/deleteCookie?name=deletedCookie'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/deleteCookie?name=deletedCookie',
String
)
def json = new JsonSlurper().parseText(response.body())
@@ -252,8 +239,8 @@ class RequestResponseSpec extends Specification {
def "set session attribute stores value in session"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/setSessionAttribute?key=testKey&value=testValue'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/setSessionAttribute?key=testKey&value=testValue',
String
)
def json = new JsonSlurper().parseText(response.body())
@@ -268,20 +255,22 @@ class RequestResponseSpec extends Specification {
def "get session attribute retrieves stored value"() {
given:
// First set a session attribute
- def setResponse = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/setSessionAttribute?key=retrieveKey&value=retrieveValue'),
+ def setResponse = httpClient.exchange(
+ '/requestResponseTest/setSessionAttribute?key=retrieveKey&value=retrieveValue',
String
)
def sessionCookie = setResponse.header('Set-Cookie')
when:
// Then retrieve it with the same session
- def getRequest = HttpRequest.GET('/requestResponseTest/getSessionAttribute?key=retrieveKey')
+ def getRequest = HttpRequest.GET(
+ '/requestResponseTest/getSessionAttribute?key=retrieveKey'
+ )
if (sessionCookie) {
def cookieValue = sessionCookie.split(';')[0]
getRequest = getRequest.header('Cookie', cookieValue)
}
- def response = client.toBlocking().exchange(getRequest, String)
+ def response = httpClient.exchange(getRequest, String)
def json = new JsonSlurper().parseText(response.body())
then:
@@ -294,8 +283,8 @@ class RequestResponseSpec extends Specification {
def "session counter increments on each request"() {
given:
// First request
- def response1 = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/sessionCounter'),
+ def response1 = httpClient.exchange(
+ '/requestResponseTest/sessionCounter',
String
)
def json1 = new JsonSlurper().parseText(response1.body())
@@ -308,7 +297,7 @@ class RequestResponseSpec extends Specification {
def cookieValue = sessionCookie.split(';')[0]
request2 = request2.header('Cookie', cookieValue)
}
- def response2 = client.toBlocking().exchange(request2, String)
+ def response2 = httpClient.exchange(request2, String)
def json2 = new JsonSlurper().parseText(response2.body())
then:
@@ -322,8 +311,8 @@ class RequestResponseSpec extends Specification {
def "get request info returns server details"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/getRequestInfo'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/getRequestInfo',
String
)
def json = new JsonSlurper().parseText(response.body())
@@ -338,8 +327,8 @@ class RequestResponseSpec extends Specification {
def "get request parameters returns query parameters"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/getRequestParameters?param1=value1¶m2=value2'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/getRequestParameters?param1=value1¶m2=value2',
String
)
def json = new JsonSlurper().parseText(response.body())
@@ -352,8 +341,8 @@ class RequestResponseSpec extends Specification {
def "set request attribute stores and retrieves value"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/setRequestAttribute?key=myAttr&value=myVal'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/setRequestAttribute?key=myAttr&value=myVal',
String
)
def json = new JsonSlurper().parseText(response.body())
@@ -369,8 +358,8 @@ class RequestResponseSpec extends Specification {
def "unicode response returns characters in multiple languages"() {
when:
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/requestResponseTest/unicodeResponse'),
+ def response = httpClient.exchange(
+ '/requestResponseTest/unicodeResponse',
String
)
def json = new JsonSlurper().parseText(response.body())
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/springevents/SpringEventsSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/springevents/SpringEventsSpec.groovy
index 94fe5deb5e..eafd73064a 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/springevents/SpringEventsSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/springevents/SpringEventsSpec.groovy
@@ -20,16 +20,14 @@ package functionaltests.springevents
import groovy.json.JsonSlurper
-import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
-import io.micronaut.http.client.HttpClient
import spock.lang.Narrative
-import spock.lang.Shared
import spock.lang.Specification
import org.springframework.beans.factory.annotation.Autowired
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for Spring application events in Grails.
@@ -43,7 +41,7 @@ Spring's ApplicationEvent mechanism allows decoupled communication between
components. Grails integrates with Spring events via @EventListener annotations
and ApplicationEventPublisher.
''')
-class SpringEventsSpec extends Specification {
+class SpringEventsSpec extends Specification implements HttpClientSupport {
@Autowired
EventListenerService eventListenerService
@@ -51,18 +49,10 @@ class SpringEventsSpec extends Specification {
@Autowired
EventPublisherService eventPublisherService
- @Shared
- HttpClient client
-
def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:$serverPort"))
eventListenerService.clearEvents()
}
- def cleanupSpec() {
- client.close()
- }
-
// ========== Basic Event Publishing ==========
def "custom event is published and received"() {
@@ -161,8 +151,8 @@ class SpringEventsSpec extends Specification {
def "event can be published via HTTP endpoint"() {
when: "calling publish endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/springEvent/publishCustom?message=HTTP+Event'),
+ def response = httpClient.exchange(
+ '/springEvent/publishCustom?message=HTTP+Event',
String
)
@@ -175,8 +165,8 @@ class SpringEventsSpec extends Specification {
def "user action event can be published via HTTP"() {
when: "calling user action publish endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/springEvent/publishUserAction?userId=web-user&userAction=CLICK'),
+ def response = httpClient.exchange(
+ '/springEvent/publishUserAction?userId=web-user&userAction=CLICK',
String
)
@@ -190,8 +180,8 @@ class SpringEventsSpec extends Specification {
def "priority event ordering works via HTTP"() {
when: "calling priority publish endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/springEvent/publishPriority?data=http-test'),
+ def response = httpClient.exchange(
+ '/springEvent/publishPriority?data=http-test',
String
)
@@ -204,8 +194,8 @@ class SpringEventsSpec extends Specification {
def "multiple events can be published via HTTP"() {
when: "calling multiple publish endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/springEvent/publishMultiple?count=3'),
+ def response = httpClient.exchange(
+ '/springEvent/publishMultiple?count=3',
String
)
@@ -218,8 +208,8 @@ class SpringEventsSpec extends Specification {
def "conditional event works via HTTP for matching message"() {
when: "publishing important message"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/springEvent/publishConditional?important=true'),
+ def response = httpClient.exchange(
+ '/springEvent/publishConditional?important=true',
String
)
@@ -231,8 +221,8 @@ class SpringEventsSpec extends Specification {
def "conditional event skipped via HTTP for non-matching message"() {
when: "publishing normal message"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/springEvent/publishConditional?important=false'),
+ def response = httpClient.exchange(
+ '/springEvent/publishConditional?important=false',
String
)
@@ -251,8 +241,8 @@ class SpringEventsSpec extends Specification {
eventPublisherService.publishUserAction('user', 'action')
when: "getting stats"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/springEvent/stats'),
+ def response = httpClient.exchange(
+ '/springEvent/stats',
String
)
@@ -271,8 +261,8 @@ class SpringEventsSpec extends Specification {
eventPublisherService.publishCustomEvent('To be cleared')
when: "clearing events"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/springEvent/clearEvents'),
+ def response = httpClient.exchange(
+ '/springEvent/clearEvents',
String
)
@@ -285,8 +275,8 @@ class SpringEventsSpec extends Specification {
def "event can be published in transactional context"() {
when: "publishing transactional event"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/springEvent/publishTransactional?message=tx-test'),
+ def response = httpClient.exchange(
+ '/springEvent/publishTransactional?message=tx-test',
String
)
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/taglib/TagLibSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/taglib/TagLibSpec.groovy
index d81c17169f..60cfa86277 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/taglib/TagLibSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/taglib/TagLibSpec.groovy
@@ -19,37 +19,25 @@
package functionaltests.taglib
import io.micronaut.http.HttpRequest
-import io.micronaut.http.client.HttpClient
-import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for GSP Tag Libraries.
* Tests both custom tag libraries and built-in Grails tags.
*/
@Integration
-class TagLibSpec extends Specification {
-
- @Shared
- HttpClient client
-
- def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:$serverPort"))
- }
-
- def cleanupSpec() {
- client.close()
- }
+class TagLibSpec extends Specification implements HttpClientSupport {
// ========== Custom Tag: hello ==========
def "custom:hello tag renders greeting with name attribute"() {
when: "calling the hello tag test endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/tagLibTest/testHelloTag?name=Grails'),
+ def response = httpClient.exchange(
+ '/tagLibTest/testHelloTag?name=Grails',
String
)
@@ -60,8 +48,8 @@ class TagLibSpec extends Specification {
def "custom:hello tag uses default name when not provided"() {
when: "calling the hello tag test endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/tagLibTest/testHelloTag'),
+ def response = httpClient.exchange(
+ '/tagLibTest/testHelloTag',
String
)
@@ -74,8 +62,8 @@ class TagLibSpec extends Specification {
def "custom:wrapper tag renders title and body content"() {
when: "calling the wrapper tag test endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/tagLibTest/testWrapperTag?title=My%20Section&content=Section%20content'),
+ def response = httpClient.exchange(
+ '/tagLibTest/testWrapperTag?title=My%20Section&content=Section%20content',
String
)
@@ -89,8 +77,8 @@ class TagLibSpec extends Specification {
def "custom:wrapper tag applies custom CSS class"() {
when: "calling the wrapper tag test endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/tagLibTest/testWrapperTag?title=Test&content=Test&cssClass=custom-wrapper'),
+ def response = httpClient.exchange(
+ '/tagLibTest/testWrapperTag?title=Test&content=Test&cssClass=custom-wrapper',
String
)
@@ -103,8 +91,8 @@ class TagLibSpec extends Specification {
def "custom:iterate tag iterates over items"() {
when: "calling the iterate tag test endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/tagLibTest/testIterateTag?items=A,B,C'),
+ def response = httpClient.exchange(
+ '/tagLibTest/testIterateTag?items=A,B,C',
String
)
@@ -118,8 +106,8 @@ class TagLibSpec extends Specification {
def "custom:iterate tag uses separator between items"() {
when: "calling the iterate tag test endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/tagLibTest/testIterateTag?items=X,Y,Z&separator=-'),
+ def response = httpClient.exchange(
+ '/tagLibTest/testIterateTag?items=X,Y,Z&separator=-',
String
)
@@ -133,8 +121,8 @@ class TagLibSpec extends Specification {
def "custom:showIf tag shows content when condition is true"() {
when: "calling the conditional tags test endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/tagLibTest/testConditionalTags?condition=true'),
+ def response = httpClient.exchange(
+ '/tagLibTest/testConditionalTags?condition=true',
String
)
@@ -147,8 +135,8 @@ class TagLibSpec extends Specification {
def "custom:hideIf tag shows content when condition is false"() {
when: "calling the conditional tags test endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/tagLibTest/testConditionalTags?condition=false'),
+ def response = httpClient.exchange(
+ '/tagLibTest/testConditionalTags?condition=false',
String
)
@@ -164,8 +152,8 @@ class TagLibSpec extends Specification {
@Unroll
def "custom:formatted tag formats value as #format"() {
when: "calling the formatted tag test endpoint"
- def response = client.toBlocking().exchange(
- HttpRequest.GET("/tagLibTest/testFormattedTag?value=${value}&format=${format}&decimals=${decimals}"),
+ def response = httpClient.exchange(
+ "/tagLibTest/testFormattedTag?value=${value}&format=${format}&decimals=${decimals}",
String
)
@@ -184,7 +172,7 @@ class TagLibSpec extends Specification {
def "custom:list tag renders unordered list by default"() {
when: "calling the list tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testListTag?items=Apple,Banana,Cherry'),
String
)
@@ -201,7 +189,7 @@ class TagLibSpec extends Specification {
def "custom:list tag renders ordered list when type is ordered"() {
when: "calling the list tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testListTag?items=First,Second,Third&type=ordered'),
String
)
@@ -217,7 +205,7 @@ class TagLibSpec extends Specification {
def "custom:panel tag renders panel with title and body"() {
when: "calling the panel tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testPanelTag?title=Info%20Panel&type=info&content=Panel%20content'),
String
)
@@ -234,7 +222,7 @@ class TagLibSpec extends Specification {
def "custom:panel tag renders collapse button when collapsible"() {
when: "calling the panel tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testPanelTag?title=Collapsible&collapsible=true'),
String
)
@@ -249,7 +237,7 @@ class TagLibSpec extends Specification {
@Unroll
def "custom:badge tag renders badge with type=#type and size=#size"() {
when: "calling the badge tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET("/tagLibTest/testBadgeTag?type=${type}&size=${size}&content=${content}"),
String
)
@@ -272,7 +260,7 @@ class TagLibSpec extends Specification {
def "custom:progress tag renders progress bar with percentage"() {
when: "calling the progress tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testProgressTag?value=75&max=100'),
String
)
@@ -288,7 +276,7 @@ class TagLibSpec extends Specification {
def "custom:progress tag hides label when showLabel is false"() {
when: "calling the progress tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testProgressTag?value=50&max=100&showLabel=false'),
String
)
@@ -304,7 +292,7 @@ class TagLibSpec extends Specification {
def "custom:repeat tag repeats body content specified times"() {
when: "calling the repeat tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testRepeatTag?times=3'),
String
)
@@ -319,7 +307,7 @@ class TagLibSpec extends Specification {
def "custom:repeat tag uses separator between repetitions"() {
when: "calling the repeat tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testRepeatTag?times=2&separator=%20-%20'),
String
)
@@ -333,7 +321,7 @@ class TagLibSpec extends Specification {
def "custom:raw tag outputs unescaped HTML content"() {
when: "calling the raw tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testRawTag'),
String
)
@@ -347,7 +335,7 @@ class TagLibSpec extends Specification {
def "custom:definitionList tag renders definition list from map"() {
when: "calling the definition list tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testDefinitionListTag'),
String
)
@@ -367,7 +355,7 @@ class TagLibSpec extends Specification {
def "custom:requestInfo tag retrieves request attributes"() {
when: "calling the request info tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testRequestInfoTag?attr=method'),
String
)
@@ -381,7 +369,7 @@ class TagLibSpec extends Specification {
def "custom:sessionValue tag displays default when session value not set"() {
when: "calling the session value tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testSessionValueTag?key=nonexistent&default=DefaultUser'),
String
)
@@ -395,7 +383,7 @@ class TagLibSpec extends Specification {
def "custom:setVar tag sets pageScope variable"() {
when: "calling the setVar tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testSetVarTag?varName=testVar&varValue=TestValue'),
String
)
@@ -410,7 +398,7 @@ class TagLibSpec extends Specification {
@Unroll
def "custom:alert tag renders #type alert"() {
when: "calling the alert tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET("/tagLibTest/testAlertTag?type=${type}&message=${URLEncoder.encode(message, 'UTF-8')}"),
String
)
@@ -431,7 +419,7 @@ class TagLibSpec extends Specification {
def "custom:alert tag renders dismissible button when dismissible=true"() {
when: "calling the alert tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testAlertTag?type=info&dismissible=true'),
String
)
@@ -447,7 +435,7 @@ class TagLibSpec extends Specification {
def "custom:join tag joins items with separator"() {
when: "calling the join tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testJoinTag?items=red,green,blue&separator=-'),
String
)
@@ -461,7 +449,7 @@ class TagLibSpec extends Specification {
def "custom:cssClass tag builds class string from boolean attributes"() {
when: "calling the cssClass tag test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testCssClassTag?base=btn&active=true&disabled=false&highlighted=true'),
String
)
@@ -479,7 +467,7 @@ class TagLibSpec extends Specification {
def "g:if tag shows content when condition is true"() {
when: "calling the built-in if test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInIf?value=10'),
String
)
@@ -493,7 +481,7 @@ class TagLibSpec extends Specification {
def "g:elseif and g:else work correctly"() {
when: "calling the built-in if test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInIf?value=30'),
String
)
@@ -507,7 +495,7 @@ class TagLibSpec extends Specification {
def "g:each tag iterates over collection"() {
when: "calling the built-in each test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInEach?items=A,B,C'),
String
)
@@ -522,7 +510,7 @@ class TagLibSpec extends Specification {
def "g:each tag provides status variable"() {
when: "calling the built-in each test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInEach?items=X,Y,Z'),
String
)
@@ -539,7 +527,7 @@ class TagLibSpec extends Specification {
def "g:collect tag transforms items"() {
when: "calling the built-in collect test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInCollect?items=apple,banana'),
String
)
@@ -555,7 +543,7 @@ class TagLibSpec extends Specification {
def "g:findAll tag filters items"() {
when: "calling the built-in findAll test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInFindAll?threshold=7'),
String
)
@@ -573,7 +561,7 @@ class TagLibSpec extends Specification {
def "g:link tag creates link with controller and action"() {
when: "calling the built-in link test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInLink?targetController=book&targetAction=show&targetId=42&linkText=View'),
String
)
@@ -590,7 +578,7 @@ class TagLibSpec extends Specification {
def "g:createLink tag creates URL string"() {
when: "calling the built-in createLink test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInCreateLink?targetController=book&targetAction=list'),
String
)
@@ -604,7 +592,7 @@ class TagLibSpec extends Specification {
def "g:form tag creates form with action"() {
when: "calling the built-in form test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInForm?targetController=book&targetAction=save'),
String
)
@@ -622,7 +610,7 @@ class TagLibSpec extends Specification {
def "g:message tag renders message with default"() {
when: "calling the built-in message test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInMessage?code=nonexistent.key&default=Default%20Message'),
String
)
@@ -636,7 +624,7 @@ class TagLibSpec extends Specification {
def "g:formatDate tag formats date"() {
when: "calling the built-in formatDate test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInFormatDate'),
String
)
@@ -651,7 +639,7 @@ class TagLibSpec extends Specification {
def "g:formatNumber tag formats number"() {
when: "calling the built-in formatNumber test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInFormatNumber?number=1234567.89'),
String
)
@@ -666,7 +654,7 @@ class TagLibSpec extends Specification {
def "g:set tag sets and updates variables"() {
when: "calling the built-in set test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInSet?initialValue=First&newValue=Second'),
String
)
@@ -682,7 +670,7 @@ class TagLibSpec extends Specification {
def "g:join tag joins items with delimiter"() {
when: "calling the built-in join test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInJoin?items=red,green,blue&delimiter=%20-%20'),
String
)
@@ -696,7 +684,7 @@ class TagLibSpec extends Specification {
def "g:include tag includes content from another action"() {
when: "calling the built-in include test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInInclude?message=Test%20Message'),
String
)
@@ -710,7 +698,7 @@ class TagLibSpec extends Specification {
def "g:render tag renders template with model"() {
when: "calling the built-in render test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInRender?text=Hello%20Template'),
String
)
@@ -724,7 +712,7 @@ class TagLibSpec extends Specification {
def "g:while tag loops while condition is true"() {
when: "calling the built-in while test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInWhile?count=3'),
String
)
@@ -742,7 +730,7 @@ class TagLibSpec extends Specification {
def "g:uploadForm tag creates multipart form"() {
when: "calling the built-in uploadForm test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInUploadForm'),
String
)
@@ -758,7 +746,7 @@ class TagLibSpec extends Specification {
def "g:select tag creates select element with options"() {
when: "calling the built-in select test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInSelect?selected=2'),
String
)
@@ -775,7 +763,7 @@ class TagLibSpec extends Specification {
def "g:radio tag creates radio buttons"() {
when: "calling the built-in radio test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInRadio?selected=Option%20B'),
String
)
@@ -791,7 +779,7 @@ class TagLibSpec extends Specification {
def "g:checkBox tag creates checkbox"() {
when: "calling the built-in checkbox test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInCheckBox?checked=true'),
String
)
@@ -807,7 +795,7 @@ class TagLibSpec extends Specification {
def "g:textArea tag creates textarea"() {
when: "calling the built-in textarea test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInTextArea?value=Test%20Content&rows=5&cols=40'),
String
)
@@ -825,7 +813,7 @@ class TagLibSpec extends Specification {
def "g:textField tag creates text input"() {
when: "calling the built-in textField test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInTextField?value=Test%20Value&maxlength=50'),
String
)
@@ -842,7 +830,7 @@ class TagLibSpec extends Specification {
def "g:passwordField tag creates password input"() {
when: "calling the built-in passwordField test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInPasswordField'),
String
)
@@ -856,7 +844,7 @@ class TagLibSpec extends Specification {
def "g:hiddenField tag creates hidden input"() {
when: "calling the built-in hiddenField test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInHiddenField?value=secret-value'),
String
)
@@ -872,7 +860,7 @@ class TagLibSpec extends Specification {
def "g:fieldValue tag extracts bean field value"() {
when: "calling the built-in fieldValue test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInFieldValue?field=title'),
String
)
@@ -886,7 +874,7 @@ class TagLibSpec extends Specification {
def "g:sortableColumn tag creates sortable table header"() {
when: "calling the built-in sortableColumn test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInSortableColumn'),
String
)
@@ -903,7 +891,7 @@ class TagLibSpec extends Specification {
def "g:paginate tag creates pagination links"() {
when: "calling the built-in paginate test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testBuiltInPaginate?total=100&max=10&offset=0'),
String
)
@@ -919,7 +907,7 @@ class TagLibSpec extends Specification {
def "nested custom tags render correctly"() {
when: "calling the nested tags test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testNestedTags'),
String
)
@@ -933,7 +921,7 @@ class TagLibSpec extends Specification {
def "tags work with complex model data"() {
when: "calling the tags with model test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testTagsWithModel'),
String
)
@@ -952,7 +940,7 @@ class TagLibSpec extends Specification {
def "encoding tags properly escape content"() {
when: "calling the encoding tags test endpoint"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/tagLibTest/testEncodingTags'),
String
)
diff --git a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/urlmappings/UrlMappingsSpec.groovy b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/urlmappings/UrlMappingsSpec.groovy
index c1e7603912..8f09470540 100644
--- a/grails-test-examples/app1/src/integration-test/groovy/functionaltests/urlmappings/UrlMappingsSpec.groovy
+++ b/grails-test-examples/app1/src/integration-test/groovy/functionaltests/urlmappings/UrlMappingsSpec.groovy
@@ -22,13 +22,12 @@ import groovy.json.JsonSlurper
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
-import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
import spock.lang.Narrative
-import spock.lang.Shared
import spock.lang.Specification
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for Grails URL mappings features.
@@ -41,24 +40,13 @@ import grails.testing.mixin.integration.Integration
Grails URL mappings provide flexible routing of HTTP requests to controller actions.
This includes path variables, constraints, HTTP method-based routing, and redirects.
''')
-class UrlMappingsSpec extends Specification {
-
- @Shared
- HttpClient client
-
- def setup() {
- client = client ?: HttpClient.create(new URL("http://localhost:$serverPort"))
- }
-
- def cleanupSpec() {
- client.close()
- }
+class UrlMappingsSpec extends Specification implements HttpClientSupport {
// ========== Static Path Mappings ==========
def "static path mapping routes to correct action"() {
when: "accessing static path"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/test'),
String
)
@@ -74,7 +62,7 @@ class UrlMappingsSpec extends Specification {
def "single path variable is captured"() {
when: "accessing path with variable"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/items/123'),
String
)
@@ -88,7 +76,7 @@ class UrlMappingsSpec extends Specification {
def "path variable accepts alphanumeric values"() {
when: "accessing path with alphanumeric id"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/items/abc-123'),
String
)
@@ -101,7 +89,7 @@ class UrlMappingsSpec extends Specification {
def "multiple path variables are captured"() {
when: "accessing path with multiple variables"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/archive/2024/03/15'),
String
)
@@ -119,7 +107,7 @@ class UrlMappingsSpec extends Specification {
def "named mapping routes correctly"() {
when: "accessing named mapping"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/named/test-name'),
String
)
@@ -135,7 +123,7 @@ class UrlMappingsSpec extends Specification {
def "constrained path accepts valid values"() {
when: "accessing with valid constrained value"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/codes/ABC'),
String
)
@@ -149,7 +137,7 @@ class UrlMappingsSpec extends Specification {
def "constrained path rejects invalid values"() {
when: "accessing with invalid constrained value (lowercase)"
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.GET('/api/codes/abc'),
String
)
@@ -161,7 +149,7 @@ class UrlMappingsSpec extends Specification {
def "constrained path rejects numeric values"() {
when: "accessing with numeric value"
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.GET('/api/codes/123'),
String
)
@@ -175,7 +163,7 @@ class UrlMappingsSpec extends Specification {
def "GET request routes to list action"() {
when: "making GET request to resources"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/resources'),
String
)
@@ -188,7 +176,7 @@ class UrlMappingsSpec extends Specification {
def "POST request routes to save action"() {
when: "making POST request to resources"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/api/resources', '{}'),
String
)
@@ -202,7 +190,7 @@ class UrlMappingsSpec extends Specification {
def "PUT request routes to update action"() {
when: "making PUT request to resources with id"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.PUT('/api/resources/42', '{}'),
String
)
@@ -217,7 +205,7 @@ class UrlMappingsSpec extends Specification {
def "DELETE request routes to delete action"() {
when: "making DELETE request to resources with id"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.DELETE('/api/resources/42'),
String
)
@@ -234,7 +222,7 @@ class UrlMappingsSpec extends Specification {
def "optional path variable with value"() {
when: "accessing with optional variable provided"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/optional/required-value/optional-value'),
String
)
@@ -248,7 +236,7 @@ class UrlMappingsSpec extends Specification {
def "optional path variable without value uses default"() {
when: "accessing without optional variable"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/optional/required-value'),
String
)
@@ -264,7 +252,7 @@ class UrlMappingsSpec extends Specification {
def "redirect mapping performs redirect"() {
when: "accessing redirect mapping"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/old-endpoint').header('Accept', '*/*'),
String
)
@@ -279,7 +267,7 @@ class UrlMappingsSpec extends Specification {
def "default mapping with controller and action"() {
when: "using default mapping pattern"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/urlMappingsTest/show/99'),
String
)
@@ -294,7 +282,7 @@ class UrlMappingsSpec extends Specification {
def "default mapping with format extension"() {
when: "using default mapping with format"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/urlMappingsTest/list.json'),
String
)
@@ -309,7 +297,7 @@ class UrlMappingsSpec extends Specification {
def "query parameters are accessible in action"() {
when: "accessing with query parameters"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/test?param1=value1¶m2=value2'),
String
)
@@ -325,7 +313,7 @@ class UrlMappingsSpec extends Specification {
def "request method is correctly detected"() {
when: "making request"
- def response = client.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/api/method-test'),
String
)
@@ -340,7 +328,7 @@ class UrlMappingsSpec extends Specification {
def "non-existent path returns 404"() {
when: "accessing non-existent path"
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.GET('/api/does-not-exist'),
String
)
diff --git a/grails-test-examples/async-events-pubsub-demo/build.gradle b/grails-test-examples/async-events-pubsub-demo/build.gradle
index 823b1e9a85..1a93ec7705 100644
--- a/grails-test-examples/async-events-pubsub-demo/build.gradle
+++ b/grails-test-examples/async-events-pubsub-demo/build.gradle
@@ -65,9 +65,9 @@ dependencies {
runtimeOnly 'org.fusesource.jansi:jansi'
runtimeOnly 'com.zaxxer:HikariCP'
- testImplementation "io.micronaut.serde:micronaut-serde-jackson:$micronautSerdeJacksonVersion"
- testImplementation "io.micronaut:micronaut-http-client:$micronautHttpClientVersion"
testImplementation 'org.apache.grails:grails-testing-support-web'
+
+ integrationTestImplementation 'org.apache.grails:grails-testing-support-httpclient'
}
bootRun {
diff --git a/grails-test-examples/async-events-pubsub-demo/src/integration-test/groovy/pubsub/demo/TaskControllerSpec.groovy b/grails-test-examples/async-events-pubsub-demo/src/integration-test/groovy/pubsub/demo/TaskControllerSpec.groovy
index da5d83e4c6..762f1a3f1c 100644
--- a/grails-test-examples/async-events-pubsub-demo/src/integration-test/groovy/pubsub/demo/TaskControllerSpec.groovy
+++ b/grails-test-examples/async-events-pubsub-demo/src/integration-test/groovy/pubsub/demo/TaskControllerSpec.groovy
@@ -16,43 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package pubsub.demo
-import grails.testing.mixin.integration.Integration
-import io.micronaut.core.type.Argument
-import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
-import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
-import spock.lang.AutoCleanup
-import spock.lang.PendingFeature
-import spock.lang.Shared
import spock.lang.Specification
-/**
- * Created by graemerocher on 30/05/2017.
- */
-@Integration
-class TaskControllerSpec extends Specification {
-
- @Shared
- @AutoCleanup
- HttpClient client
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- void setup() {
- client = HttpClient.create("http://localhost:$serverPort".toURL())
- }
+@Integration
+class TaskControllerSpec extends Specification implements HttpClientSupport {
void 'test async error handling'() {
-
when: 'we invoke an endpoint that throws an exception'
- def request = HttpRequest.GET('/task/error')
- client.toBlocking().exchange(request, Argument.of(String), Argument.of(String))
+ httpClient.retrieve('/task/error')
then: 'the response is as expected'
- def e = thrown(HttpClientResponseException)
- e.response.status == HttpStatus.INTERNAL_SERVER_ERROR
- e.response.body() == 'error occurred'
+ def e = thrown(HttpClientResponseException)
+ e.response.status == HttpStatus.INTERNAL_SERVER_ERROR
+ e.response.body() == 'error occurred'
}
}
diff --git a/grails-test-examples/cache/build.gradle b/grails-test-examples/cache/build.gradle
index a57a3f8d1f..05bce3f556 100644
--- a/grails-test-examples/cache/build.gradle
+++ b/grails-test-examples/cache/build.gradle
@@ -63,13 +63,13 @@ dependencies {
runtimeOnly 'org.apache.tomcat:tomcat-jdbc'
runtimeOnly 'org.fusesource.jansi:jansi'
- integrationTestImplementation testFixtures('org.apache.grails:grails-geb')
- integrationTestImplementation "io.micronaut:micronaut-http-client:$micronautHttpClientVersion"
- integrationTestImplementation "io.micronaut.serde:micronaut-serde-jackson:$micronautSerdeJacksonVersion"
-
testImplementation 'org.apache.grails:grails-testing-support-datamapping'
testImplementation 'org.apache.grails:grails-testing-support-web'
testImplementation 'org.spockframework:spock-core'
+
+ integrationTestImplementation testFixtures('org.apache.grails:grails-geb')
+ integrationTestImplementation 'org.apache.grails:grails-testing-support-httpclient'
+
}
apply {
diff --git a/grails-test-examples/cache/src/integration-test/groovy/com/demo/AdvancedCachingIntegrationSpec.groovy b/grails-test-examples/cache/src/integration-test/groovy/com/demo/AdvancedCachingIntegrationSpec.groovy
index cedb163fdc..a606633611 100644
--- a/grails-test-examples/cache/src/integration-test/groovy/com/demo/AdvancedCachingIntegrationSpec.groovy
+++ b/grails-test-examples/cache/src/integration-test/groovy/com/demo/AdvancedCachingIntegrationSpec.groovy
@@ -16,18 +16,19 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package com.demo
-import grails.plugin.geb.ContainerGebSpec
-import grails.testing.mixin.integration.Integration
import groovy.json.JsonSlurper
+
import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import io.micronaut.http.client.HttpClient
-import org.springframework.beans.factory.annotation.Autowired
import spock.lang.Narrative
+import spock.lang.Specification
+
+import org.springframework.beans.factory.annotation.Autowired
+
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
/**
* Integration tests for advanced @Cacheable scenarios via HTTP endpoints.
@@ -45,15 +46,11 @@ Advanced Grails caching scenarios tested via HTTP endpoints.
These tests verify that complex caching behaviors work correctly
in a full application context.
''')
-class AdvancedCachingIntegrationSpec extends ContainerGebSpec {
+class AdvancedCachingIntegrationSpec extends Specification implements HttpClientSupport {
@Autowired
AdvancedCachingService advancedCachingService
- private HttpClient createClient() {
- HttpClient.create(new URL("http://localhost:$serverPort"))
- }
-
def setup() {
// Evict all caches before each test to ensure clean state
advancedCachingService.evictNullCache()
@@ -67,15 +64,12 @@ class AdvancedCachingIntegrationSpec extends ContainerGebSpec {
// ========== collection caching integration tests ==========
def "list data is cached via HTTP"() {
- given:
- def client = createClient()
-
when: "fetching list data twice"
- HttpResponse<String> response1 = client.toBlocking().exchange(
+ def response1 = httpClient.exchange(
HttpRequest.GET('/advancedCaching/listData?category=books'),
String
)
- HttpResponse<String> response2 = client.toBlocking().exchange(
+ def response2 = httpClient.exchange(
HttpRequest.GET('/advancedCaching/listData?category=books'),
String
)
@@ -88,21 +82,15 @@ class AdvancedCachingIntegrationSpec extends ContainerGebSpec {
json1.data == json2.data
json1.data.size() == 3
json1.data[0].startsWith('Item 1 for books')
-
- cleanup:
- client?.close()
}
def "map data is cached via HTTP"() {
- given:
- def client = createClient()
-
when: "fetching map data twice"
- HttpResponse<String> response1 = client.toBlocking().exchange(
+ def response1 = httpClient.exchange(
HttpRequest.GET('/advancedCaching/mapData?key=mykey'),
String
)
- HttpResponse<String> response2 = client.toBlocking().exchange(
+ def response2 = httpClient.exchange(
HttpRequest.GET('/advancedCaching/mapData?key=mykey'),
String
)
@@ -116,21 +104,15 @@ class AdvancedCachingIntegrationSpec extends ContainerGebSpec {
json1.data.key == 'mykey'
json1.data.value == 'Value for mykey'
json1.data.nested.a == 1
-
- cleanup:
- client?.close()
}
def "different categories have separate list cache entries via HTTP"() {
- given:
- def client = createClient()
-
when: "fetching different categories"
- HttpResponse<String> booksResponse = client.toBlocking().exchange(
+ def booksResponse = httpClient.exchange(
HttpRequest.GET('/advancedCaching/listData?category=books'),
String
)
- HttpResponse<String> moviesResponse = client.toBlocking().exchange(
+ def moviesResponse = httpClient.exchange(
HttpRequest.GET('/advancedCaching/listData?category=movies'),
String
)
@@ -143,40 +125,28 @@ class AdvancedCachingIntegrationSpec extends ContainerGebSpec {
books.data != movies.data
books.data[0].startsWith('Item 1 for books')
movies.data[0].startsWith('Item 1 for movies')
-
- cleanup:
- client?.close()
}
// ========== exception handling integration tests ==========
def "exception is thrown and not cached via HTTP"() {
- given:
- def client = createClient()
-
when: "calling endpoint that throws exception"
- client.toBlocking().exchange(
+ httpClient.exchange(
HttpRequest.GET('/advancedCaching/dataOrThrow?input=error'),
String
)
then: "exception results in error response"
thrown(Exception)
-
- cleanup:
- client?.close()
}
def "successful calls are cached even after exceptions via HTTP"() {
- given:
- def client = createClient()
-
when: "calling with normal value twice"
- HttpResponse<String> response1 = client.toBlocking().exchange(
+ def response1 = httpClient.exchange(
HttpRequest.GET('/advancedCaching/dataOrThrow?input=normal'),
String
)
- HttpResponse<String> response2 = client.toBlocking().exchange(
+ def response2 = httpClient.exchange(
HttpRequest.GET('/advancedCaching/dataOrThrow?input=normal'),
String
)
@@ -187,26 +157,22 @@ class AdvancedCachingIntegrationSpec extends ContainerGebSpec {
def json1 = new JsonSlurper().parseText(response1.body())
def json2 = new JsonSlurper().parseText(response2.body())
json1.data == json2.data
-
- cleanup:
- client?.close()
}
// ========== eviction integration tests ==========
def "eviction clears list cache via HTTP"() {
given:
- def client = createClient()
// First call to populate cache
- def first = client.toBlocking().exchange(
+ def first = httpClient.exchange(
HttpRequest.GET('/advancedCaching/listData?category=books'),
String
)
def firstData = new JsonSlurper().parseText(first.body()).data
when: "evicting cache and fetching again"
- client.toBlocking().exchange(HttpRequest.GET('/advancedCaching/evictListCache'), String)
- HttpResponse<String> second = client.toBlocking().exchange(
+ httpClient.retrieve('/advancedCaching/evictListCache')
+ def second = httpClient.exchange(
HttpRequest.GET('/advancedCaching/listData?category=books'),
String
)
@@ -214,24 +180,20 @@ class AdvancedCachingIntegrationSpec extends ContainerGebSpec {
then: "new data is generated after eviction"
firstData != secondData
-
- cleanup:
- client?.close()
}
def "eviction clears map cache via HTTP"() {
given:
- def client = createClient()
// First call to populate cache
- def first = client.toBlocking().exchange(
+ def first = httpClient.exchange(
HttpRequest.GET('/advancedCaching/mapData?key=mykey'),
String
)
def firstData = new JsonSlurper().parseText(first.body()).data
when: "evicting cache and fetching again"
- client.toBlocking().exchange(HttpRequest.GET('/advancedCaching/evictMapCache'), String)
- HttpResponse<String> second = client.toBlocking().exchange(
+ httpClient.retrieve('/advancedCaching/evictMapCache')
+ def second = httpClient.exchange(
HttpRequest.GET('/advancedCaching/mapData?key=mykey'),
String
)
@@ -239,23 +201,17 @@ class AdvancedCachingIntegrationSpec extends ContainerGebSpec {
then: "new data is generated after eviction"
firstData != secondData
-
- cleanup:
- client?.close()
}
// ========== custom key caching integration tests ==========
def "custom key caching works via HTTP"() {
- given:
- def client = createClient()
-
when: "fetching data by custom key twice"
- HttpResponse<String> response1 = client.toBlocking().exchange(
+ def response1 = httpClient.exchange(
HttpRequest.GET('/advancedCaching/getDataByKey?key=testkey'),
String
)
- HttpResponse<String> response2 = client.toBlocking().exchange(
+ def response2 = httpClient.exchange(
HttpRequest.GET('/advancedCaching/getDataByKey?key=testkey'),
String
)
@@ -266,24 +222,20 @@ class AdvancedCachingIntegrationSpec extends ContainerGebSpec {
def json1 = new JsonSlurper().parseText(response1.body())
def json2 = new JsonSlurper().parseText(response2.body())
json1.data == json2.data
-
- cleanup:
- client?.close()
}
def "eviction by custom key works via HTTP"() {
given:
- def client = createClient()
// First call to populate cache
- def first = client.toBlocking().exchange(
+ def first = httpClient.exchange(
HttpRequest.GET('/advancedCaching/getDataByKey?key=mykey'),
String
)
def firstData = new JsonSlurper().parseText(first.body()).data
when: "evicting by key and fetching again"
- client.toBlocking().exchange(HttpRequest.GET('/advancedCaching/evictByKey?key=mykey'), String)
- HttpResponse<String> second = client.toBlocking().exchange(
+ httpClient.retrieve('/advancedCaching/evictByKey?key=mykey')
+ def second = httpClient.exchange(
HttpRequest.GET('/advancedCaching/getDataByKey?key=mykey'),
String
)
@@ -291,24 +243,20 @@ class AdvancedCachingIntegrationSpec extends ContainerGebSpec {
then: "new data is generated after eviction"
firstData != secondData
-
- cleanup:
- client?.close()
}
def "eviction of all custom key cache works via HTTP"() {
given:
- def client = createClient()
// First call to populate cache
- def first = client.toBlocking().exchange(
+ def first = httpClient.exchange(
HttpRequest.GET('/advancedCaching/getDataByKey?key=anykey'),
String
)
def firstData = new JsonSlurper().parseText(first.body()).data
when: "evicting all and fetching again"
- client.toBlocking().exchange(HttpRequest.GET('/advancedCaching/evictAllKeyCache'), String)
- HttpResponse<String> second = client.toBlocking().exchange(
+ httpClient.retrieve('/advancedCaching/evictAllKeyCache')
+ def second = httpClient.exchange(
HttpRequest.GET('/advancedCaching/getDataByKey?key=anykey'),
String
)
@@ -316,8 +264,5 @@ class AdvancedCachingIntegrationSpec extends ContainerGebSpec {
then: "new data is generated after eviction"
firstData != secondData
-
- cleanup:
- client?.close()
}
}
diff --git a/grails-test-examples/hibernate5/grails-multiple-datasources/build.gradle b/grails-test-examples/hibernate5/grails-multiple-datasources/build.gradle
index 005369f776..b7f6967aa3 100644
--- a/grails-test-examples/hibernate5/grails-multiple-datasources/build.gradle
+++ b/grails-test-examples/hibernate5/grails-multiple-datasources/build.gradle
@@ -51,12 +51,10 @@ dependencies {
testImplementation 'org.apache.grails.testing:grails-testing-support-core'
- integrationTestImplementation "io.micronaut:micronaut-http-client:$micronautHttpClientVersion"
- integrationTestImplementation 'org.spockframework:spock-core'
-
- integrationTestRuntimeOnly "io.micronaut.serde:micronaut-serde-jackson:$micronautSerdeJacksonVersion"
-
testRuntimeOnly 'org.apache.grails:grails-testing-support-web'
+
+ integrationTestImplementation 'org.apache.grails:grails-testing-support-httpclient'
+ integrationTestImplementation 'org.spockframework:spock-core'
}
sourceSets {
diff --git a/grails-test-examples/hibernate5/grails-multiple-datasources/src/integration-test/groovy/functionaltests/MultiDataSourceWithSessionSpec.groovy b/grails-test-examples/hibernate5/grails-multiple-datasources/src/integration-test/groovy/functionaltests/MultiDataSourceWithSessionSpec.groovy
index 6a8c650809..1441b86812 100644
--- a/grails-test-examples/hibernate5/grails-multiple-datasources/src/integration-test/groovy/functionaltests/MultiDataSourceWithSessionSpec.groovy
+++ b/grails-test-examples/hibernate5/grails-multiple-datasources/src/integration-test/groovy/functionaltests/MultiDataSourceWithSessionSpec.groovy
@@ -16,35 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functionaltests
-import datasources.Application
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.OnceBefore
-import io.micronaut.http.client.HttpClient
import spock.lang.Issue
-import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Stepwise
-@Integration(applicationClass = Application)
-@Stepwise
-class MultiDataSourceWithSessionSpec extends Specification {
-
- @Shared
- HttpClient client
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @OnceBefore
- void init() {
- String baseUrl = "http://localhost:$serverPort"
- this.client = HttpClient.create(baseUrl.toURL())
- }
+@Stepwise
+@Integration
+class MultiDataSourceWithSessionSpec extends Specification implements HttpClientSupport {
@Issue('https://github.com/apache/grails-core/issues/14333')
void "withSession on secondary datasource does not throw No Session found"() {
when:
- String response = client.toBlocking().retrieve('/secondaryBook/withSessionTest')
+ def response = httpClient.retrieve('/secondaryBook/withSessionTest')
then:
response.contains('sessionObtained:true')
@@ -53,19 +41,19 @@ class MultiDataSourceWithSessionSpec extends Specification {
@Issue('https://github.com/apache/grails-core/issues/14333')
void "CRUD via withSession on secondary datasource works"() {
when:
- String response = client.toBlocking().retrieve('/secondaryBook/crudViaWithSession')
+ def response = httpClient.retrieve('/secondaryBook/crudViaWithSession')
then:
response.contains('count:1')
cleanup:
- client.toBlocking().retrieve('/secondaryBook/cleanup')
+ httpClient.retrieve('/secondaryBook/cleanup')
}
@Issue('https://github.com/apache/grails-core/issues/11798')
void "domain class on secondary datasource can be validated via withSession"() {
when:
- String response = client.toBlocking().retrieve('/secondaryBook/validateCommandObject')
+ def response = httpClient.retrieve('/secondaryBook/validateCommandObject')
then:
response.contains('validated:true')
@@ -75,12 +63,12 @@ class MultiDataSourceWithSessionSpec extends Specification {
@Issue('https://github.com/apache/grails-core/issues/14333')
void "withSession works after executeUpdate on secondary datasource"() {
when:
- String response = client.toBlocking().retrieve('/secondaryBook/sessionAfterExecuteUpdate')
+ def response = httpClient.retrieve('/secondaryBook/sessionAfterExecuteUpdate')
then:
response.contains('title:After Update')
cleanup:
- client.toBlocking().retrieve('/secondaryBook/cleanup')
+ httpClient.retrieve('/secondaryBook/cleanup')
}
}
diff --git a/grails-test-examples/hibernate5/issue450/build.gradle b/grails-test-examples/hibernate5/issue450/build.gradle
index f48f21355f..87a07aab2b 100644
--- a/grails-test-examples/hibernate5/issue450/build.gradle
+++ b/grails-test-examples/hibernate5/issue450/build.gradle
@@ -57,11 +57,10 @@ dependencies {
runtimeOnly 'org.springframework.boot:spring-boot-starter-logging'
runtimeOnly 'org.springframework.boot:spring-boot-starter-tomcat'
- integrationTestImplementation "io.micronaut:micronaut-http-client:$micronautHttpClientVersion"
+ integrationTestImplementation 'org.apache.grails:grails-testing-support-httpclient'
integrationTestImplementation 'org.apache.grails.testing:grails-testing-support-core'
integrationTestImplementation 'org.spockframework:spock-core'
- integrationTestRuntimeOnly "io.micronaut.serde:micronaut-serde-jackson:$micronautSerdeJacksonVersion"
}
apply {
diff --git a/grails-test-examples/hibernate5/issue450/src/integration-test/groovy/example/BookControllerSpec.groovy b/grails-test-examples/hibernate5/issue450/src/integration-test/groovy/example/BookControllerSpec.groovy
index cd9bd452eb..454f45d865 100644
--- a/grails-test-examples/hibernate5/issue450/src/integration-test/groovy/example/BookControllerSpec.groovy
+++ b/grails-test-examples/hibernate5/issue450/src/integration-test/groovy/example/BookControllerSpec.groovy
@@ -16,33 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package example
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.OnceBefore
-import spock.lang.Shared
import spock.lang.Specification
-import io.micronaut.http.client.HttpClient
-
-@Integration
-class BookControllerSpec extends Specification {
- @Shared
- HttpClient client
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @OnceBefore
- void init() {
- String baseUrl = "http://localhost:$serverPort"
- this.client = HttpClient.create(baseUrl.toURL())
- }
+@Integration
+class BookControllerSpec extends Specification implements HttpClientSupport {
void 'test books can be fetched'() {
expect:
- client.toBlocking().retrieve('/book/grails').contains('The definitive Guide to Grails 2')
- !client.toBlocking().retrieve('/book/grails').contains('Groovy in Action')
+ httpClient.retrieve('/book/grails').contains('The definitive Guide to Grails 2')
+ !httpClient.retrieve('/book/grails').contains('Groovy in Action')
- client.toBlocking().retrieve('/book/groovy').contains('Groovy in Action')
- !client.toBlocking().retrieve('/book/groovy').contains('The definitive Guide to Grails 2')
+ httpClient.retrieve('/book/groovy').contains('Groovy in Action')
+ !httpClient.retrieve('/book/groovy').contains('The definitive Guide to Grails 2')
}
}
diff --git a/grails-test-examples/issue-11102/build.gradle b/grails-test-examples/issue-11102/build.gradle
index 369fad6715..f635aa8e65 100644
--- a/grails-test-examples/issue-11102/build.gradle
+++ b/grails-test-examples/issue-11102/build.gradle
@@ -72,10 +72,8 @@ dependencies {
testImplementation 'org.apache.grails:grails-testing-support-web'
testImplementation 'org.apache.grails:grails-testing-support-datamapping'
- testImplementation "io.micronaut:micronaut-http-client:$micronautHttpClientVersion"
- testImplementation "io.micronaut.serde:micronaut-serde-jackson:$micronautSerdeJacksonVersion"
-
- integrationTestImplementation testFixtures("org.apache.grails:grails-geb")
+ integrationTestImplementation testFixtures('org.apache.grails:grails-geb')
+ integrationTestImplementation 'org.apache.grails:grails-testing-support-httpclient'
}
tasks.named('assetCompile').configure {
diff --git a/grails-test-examples/issue-11102/src/integration-test/groovy/issue11102/HttpClientCommonSpec.groovy b/grails-test-examples/issue-11102/src/integration-test/groovy/issue11102/HttpClientCommonSpec.groovy
deleted file mode 100644
index 5a1f138aa7..0000000000
--- a/grails-test-examples/issue-11102/src/integration-test/groovy/issue11102/HttpClientCommonSpec.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package issue11102
-
-import io.micronaut.http.client.HttpClient
-import spock.lang.AutoCleanup
-import spock.lang.Shared
-import spock.lang.Specification
-
-class HttpClientCommonSpec extends Specification {
-
- @Shared
- @AutoCleanup
- HttpClient client
-
- @Shared
- String baseUrl
-
- /**
- * Move this to the subclass as after Spock 2.0-M3-groovy-3.0, {code}OnceBefore{/code} does not
- * run when present in the super class.
- */
- /*@OnceBefore
- void init() {
- this.baseUrl = "http://localhost:$serverPort"
- this.client = HttpClient.create(new URL(baseUrl))
- }*/
-}
diff --git a/grails-test-examples/issue-11102/src/integration-test/groovy/issue11102/TestControllerSpec.groovy b/grails-test-examples/issue-11102/src/integration-test/groovy/issue11102/TestControllerSpec.groovy
index 0719437696..948a76656a 100644
--- a/grails-test-examples/issue-11102/src/integration-test/groovy/issue11102/TestControllerSpec.groovy
+++ b/grails-test-examples/issue-11102/src/integration-test/groovy/issue11102/TestControllerSpec.groovy
@@ -16,35 +16,27 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package issue11102
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.OnceBefore
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import io.micronaut.http.client.HttpClient
+import spock.lang.Specification
-@Integration
-class TestControllerSpec extends HttpClientCommonSpec {
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @OnceBefore
- void init() {
- this.baseUrl = "http://localhost:$serverPort"
- this.client = HttpClient.create(new URL(baseUrl))
- }
+@Integration
+class TestControllerSpec extends Specification implements HttpClientSupport {
void 'can forward a request from a GET to another GET action'() {
when: 'getting the get1 action'
- HttpResponse<String> response = client.toBlocking().exchange(HttpRequest.GET("/get1"), String)
+ def response = httpClient.exchange('/get1', String)
then: 'it is executed correctly'
response.status == HttpStatus.OK
response.body() == 'GET1'
when: 'executing an action with a forward to the other one'
- HttpResponse<String> response2 = client.toBlocking().exchange(HttpRequest.GET("/get2"), String)
+ def response2 = httpClient.exchange('/get2', String)
then: 'the request is forwarded'
response2.status == HttpStatus.OK
diff --git a/grails-test-examples/issue-11767/build.gradle b/grails-test-examples/issue-11767/build.gradle
index 28542fc268..313138ccc4 100644
--- a/grails-test-examples/issue-11767/build.gradle
+++ b/grails-test-examples/issue-11767/build.gradle
@@ -33,13 +33,10 @@ dependencies {
implementation 'org.apache.grails:grails-micronaut'
implementation project(':grails-test-examples-plugins-issue-11767')
- testImplementation platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion")
- testImplementation "io.micronaut:micronaut-http-client"
- testImplementation "io.micronaut.serde:micronaut-serde-jackson"
- testImplementation 'io.micronaut:micronaut-inject-groovy'
-
testImplementation 'org.apache.grails:grails-testing-support-web'
testImplementation 'org.spockframework:spock-core'
+
+ integrationTestImplementation 'org.apache.grails:grails-testing-support-httpclient'
}
apply {
diff --git a/grails-test-examples/issue-11767/src/integration-test/groovy/issue11767/app/ConfigLoadingSpec.groovy b/grails-test-examples/issue-11767/src/integration-test/groovy/issue11767/app/ConfigLoadingSpec.groovy
index 63c15bfc4e..fedb245cbe 100644
--- a/grails-test-examples/issue-11767/src/integration-test/groovy/issue11767/app/ConfigLoadingSpec.groovy
+++ b/grails-test-examples/issue-11767/src/integration-test/groovy/issue11767/app/ConfigLoadingSpec.groovy
@@ -16,44 +16,31 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package issue11767.app
-import grails.testing.mixin.integration.Integration
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.client.HttpClient
-import spock.lang.AutoCleanup
-import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
-@Integration
-class ConfigLoadingSpec extends Specification {
-
- @Shared
- @AutoCleanup
- HttpClient httpClient
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- void setup() {
- def baseUrl = "http://localhost:$serverPort"
- httpClient = HttpClient.create(baseUrl.toURL())
- }
+@Integration
+class ConfigLoadingSpec extends Specification implements HttpClientSupport {
@Unroll
void '#beanType beans can load plugin config values'(String beanType, String expectedResponseValue) {
when: 'The app controller is visited'
- def request = HttpRequest.GET('/app')
- String response = httpClient.toBlocking().retrieve(request, String)
+ def response = httpClient.retrieve('/app')
then: 'The value from the plugin is found'
- response.contains(expectedResponseValue)
+ response.contains(expectedResponseValue)
where:
- beanType || expectedResponseValue
- 'Plugin Groovy Spring' || 'Plugin Groovy Spring Bean - my.value2: this is value 2 from plugin.yml'
- 'Plugin Groovy Micronaut' || 'Plugin Groovy Micronaut Bean - my.value2: this is value 2 from plugin.yml'
- 'Plugin Java Micronaut' || 'Plugin Java Micronaut Bean - my.value2: this is value 2 from plugin.yml'
- 'App Groovy Micronaut' || 'App Groovy Micronaut Bean - my.value2: this is value 2 from plugin.yml'
+ beanType || expectedResponseValue
+ 'Plugin Groovy Spring' || 'Plugin Groovy Spring Bean - my.value2: this is value 2 from plugin.yml'
+ 'Plugin Groovy Micronaut' || 'Plugin Groovy Micronaut Bean - my.value2: this is value 2 from plugin.yml'
+ 'Plugin Java Micronaut' || 'Plugin Java Micronaut Bean - my.value2: this is value 2 from plugin.yml'
+ 'App Groovy Micronaut' || 'App Groovy Micronaut Bean - my.value2: this is value 2 from plugin.yml'
}
}
diff --git a/grails-test-examples/issue-15228/build.gradle b/grails-test-examples/issue-15228/build.gradle
index 8a20bff9ad..dc2fa80b31 100644
--- a/grails-test-examples/issue-15228/build.gradle
+++ b/grails-test-examples/issue-15228/build.gradle
@@ -42,8 +42,7 @@ dependencies {
testImplementation 'org.apache.grails.testing:grails-testing-support-core'
integrationTestImplementation 'com.fasterxml.jackson.core:jackson-databind'
- integrationTestImplementation "io.micronaut:micronaut-http-client:$micronautHttpClientVersion"
- integrationTestImplementation "io.micronaut:micronaut-jackson-databind:$micronautHttpClientVersion"
+ integrationTestImplementation 'org.apache.grails:grails-testing-support-httpclient'
}
apply {
diff --git a/grails-test-examples/issue-15228/src/integration-test/groovy/issue11767/app/GsonViewRespondSpec.groovy b/grails-test-examples/issue-15228/src/integration-test/groovy/issue11767/app/GsonViewRespondSpec.groovy
index 82d8adbea5..46a488c208 100644
--- a/grails-test-examples/issue-15228/src/integration-test/groovy/issue11767/app/GsonViewRespondSpec.groovy
+++ b/grails-test-examples/issue-15228/src/integration-test/groovy/issue11767/app/GsonViewRespondSpec.groovy
@@ -16,45 +16,32 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package issue11767.app
-
import com.fasterxml.jackson.databind.ObjectMapper
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
-import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
-import spock.lang.AutoCleanup
-import spock.lang.Shared
import spock.lang.Specification
+import org.springframework.beans.factory.annotation.Autowired
+
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
@Integration
-class GsonViewRespondSpec extends Specification {
+class GsonViewRespondSpec extends Specification implements HttpClientSupport {
- @Shared
+ @Autowired
ObjectMapper objectMapper
- def setupSpec() {
- objectMapper = new ObjectMapper()
- }
-
- @Shared
- @AutoCleanup
- HttpClient httpClient
-
- void setup() {
- def baseUrl = "http://localhost:$serverPort"
- httpClient = HttpClient.create(baseUrl.toURL())
- }
-
void 'respond with Error gson view'() {
when: 'The app controller is visited on errorView'
- def request = HttpRequest.GET('/app/errorView?foo=Too+Short').accept(MediaType.APPLICATION_JSON)
- httpClient.toBlocking().exchange(request, String)
+ httpClient.exchange(
+ HttpRequest.GET('/app/errorView?foo=Too+Short').accept(MediaType.APPLICATION_JSON),
+ String
+ )
then:
def ex = thrown(HttpClientResponseException)
@@ -77,8 +64,10 @@ class GsonViewRespondSpec extends Specification {
void 'respond with gson view from action name'() {
when: 'The app controller is visited on normalView'
- def request = HttpRequest.GET('/app/normalView?foo=Testing+normal+view').accept(MediaType.APPLICATION_JSON)
- def response = httpClient.toBlocking().exchange(request, String)
+ def response = httpClient.exchange(
+ HttpRequest.GET('/app/normalView?foo=Testing+normal+view').accept(MediaType.APPLICATION_JSON),
+ String
+ )
then:
objectMapper.readTree(response.body() as String) == objectMapper.readTree('''{
@@ -90,8 +79,10 @@ class GsonViewRespondSpec extends Specification {
void 'respond with gson view from type'() {
when: 'The app controller is visited on typeView'
- def request = HttpRequest.GET('/app/typeView?foo=Testing+type+view').accept(MediaType.APPLICATION_JSON)
- def response = httpClient.toBlocking().exchange(request, String)
+ def response = httpClient.exchange(
+ HttpRequest.GET('/app/typeView?foo=Testing+type+view').accept(MediaType.APPLICATION_JSON),
+ String
+ )
then:
objectMapper.readTree(response.body() as String) == objectMapper.readTree('''{
diff --git a/grails-test-examples/issue-views-182/build.gradle b/grails-test-examples/issue-views-182/build.gradle
index 068f255383..c230969b36 100644
--- a/grails-test-examples/issue-views-182/build.gradle
+++ b/grails-test-examples/issue-views-182/build.gradle
@@ -67,8 +67,7 @@ dependencies {
testImplementation 'org.apache.grails:grails-testing-support-web'
testImplementation 'org.apache.grails:grails-testing-support-datamapping'
- testImplementation "io.micronaut:micronaut-http-client:$micronautHttpClientVersion"
- testImplementation "io.micronaut.serde:micronaut-serde-jackson:$micronautSerdeJacksonVersion"
+ integrationTestImplementation 'org.apache.grails:grails-testing-support-httpclient'
}
bootRun {
diff --git a/grails-test-examples/issue-views-182/src/integration-test/groovy/views182/CustomErrorSpec.groovy b/grails-test-examples/issue-views-182/src/integration-test/groovy/views182/CustomErrorSpec.groovy
index 87730772c9..1ba7733dc2 100644
--- a/grails-test-examples/issue-views-182/src/integration-test/groovy/views182/CustomErrorSpec.groovy
+++ b/grails-test-examples/issue-views-182/src/integration-test/groovy/views182/CustomErrorSpec.groovy
@@ -16,35 +16,24 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package views182
-import grails.gorm.transactions.Rollback
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.OnceBefore
-import io.micronaut.core.type.Argument
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
+import spock.lang.Specification
-@Integration
-@Rollback
-class CustomErrorSpec extends HttpClientCommonSpec {
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @OnceBefore
- void init() {
- this.baseUrl = "http://localhost:$serverPort"
- this.client = HttpClient.create(new URL(baseUrl))
- }
+@Integration
+class CustomErrorSpec extends Specification implements HttpClientSupport {
void 'it is possible to use gson views for handling exception errors'() {
when: 'executing get to custom error'
- HttpResponse<String> response = client.toBlocking().exchange(HttpRequest.GET("/customError"), Argument.of(String), Argument.of(String))
+ httpClient.retrieve('/customError')
then:
- HttpClientResponseException e = thrown()
+ def e = thrown(HttpClientResponseException)
e.response.status == HttpStatus.INTERNAL_SERVER_ERROR
e.response.body() == '{"message":"My custom exception handler","error":500}'
}
diff --git a/grails-test-examples/issue-views-182/src/integration-test/groovy/views182/HttpClientCommonSpec.groovy b/grails-test-examples/issue-views-182/src/integration-test/groovy/views182/HttpClientCommonSpec.groovy
deleted file mode 100644
index 3e0136d256..0000000000
--- a/grails-test-examples/issue-views-182/src/integration-test/groovy/views182/HttpClientCommonSpec.groovy
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package views182
-
-import grails.testing.spock.OnceBefore
-import io.micronaut.http.client.HttpClient
-import spock.lang.AutoCleanup
-import spock.lang.Shared
-import spock.lang.Specification
-
-class HttpClientCommonSpec extends Specification {
-
- @Shared
- @AutoCleanup
- HttpClient client
-
- @Shared
- String baseUrl
-
- /**
- * Move this to the subclass as after Spock 2.0-M3-groovy-3.0, {code}OnceBefore{/code} does not
- * run when present in the super class.
- */
- /*@OnceBefore
- void init() {
- this.baseUrl = "http://localhost:$serverPort"
- this.client = HttpClient.create(new URL(baseUrl))
- }*/
-}
diff --git a/grails-test-examples/micronaut/build.gradle b/grails-test-examples/micronaut/build.gradle
index 75255ce0cc..c9e463e526 100644
--- a/grails-test-examples/micronaut/build.gradle
+++ b/grails-test-examples/micronaut/build.gradle
@@ -56,11 +56,11 @@ dependencies {
runtimeOnly 'com.h2database:h2'
runtimeOnly 'org.apache.tomcat:tomcat-jdbc'
- testImplementation "io.micronaut:micronaut-http-client:$micronautHttpClientVersion"
testImplementation 'org.apache.grails:grails-dependencies-test'
integrationTestImplementation testFixtures('org.apache.grails:grails-geb')
integrationTestImplementation "io.github.cjstehno.ersatz:ersatz:$ersatzVersion"
+ integrationTestImplementation 'org.apache.grails:grails-testing-support-httpclient'
}
apply {
diff --git a/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautDeclarativeClientSpec.groovy b/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautDeclarativeClientSpec.groovy
index 711d0bf0f1..1ca7dd7047 100644
--- a/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautDeclarativeClientSpec.groovy
+++ b/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautDeclarativeClientSpec.groovy
@@ -20,17 +20,14 @@ package micronaut
import io.github.cjstehno.ersatz.ErsatzServer
import io.github.cjstehno.ersatz.cfg.ContentType
+import io.github.cjstehno.ersatz.cfg.ServerConfig
import io.micronaut.context.ApplicationContext as MicronautApplicationContext
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.MediaType
-import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
import micronaut.client.MicronautTestClient
import spock.lang.AutoCleanup
import spock.lang.Specification
import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.beans.factory.annotation.Value
import grails.testing.mixin.integration.Integration
@@ -40,11 +37,8 @@ class MicronautDeclarativeClientSpec extends Specification {
@Autowired
MicronautApplicationContext micronautContext
- @Value('${local.server.port}')
- Integer serverPort
-
@AutoCleanup
- ErsatzServer ersatz = new ErsatzServer({ cfg ->
+ ErsatzServer ersatz = new ErsatzServer({ ServerConfig cfg ->
cfg.httpPort(19876)
})
@@ -87,20 +81,6 @@ class MicronautDeclarativeClientSpec extends Specification {
ersatz.verify()
}
- void "Micronaut HttpClient can reach the running Grails application"() {
- given: 'a Micronaut HTTP client targeting the running server'
- def client = HttpClient.create("http://localhost:$serverPort".toURL())
-
- when: 'calling the root endpoint'
- def response = client.toBlocking().exchange('/')
-
- then: 'the server responds successfully'
- response.status.code == 200
-
- cleanup:
- client.close()
- }
-
void "declarative @Client sends POST and receives mock response"() {
given: 'an ersatz server mocking a 201 Created response for POST'
ersatz.expectations({ expect ->
@@ -263,37 +243,4 @@ class MicronautDeclarativeClientSpec extends Specification {
and: 'the ersatz server received exactly one request'
ersatz.verify()
}
-
- void "Micronaut HttpClient can set Accept header"() {
- given: 'an ersatz server expecting an Accept header'
- ersatz.expectations({ expect ->
- expect.GET('/micronaut-test', { req ->
- req.called(1)
- req.header('Accept', MediaType.APPLICATION_JSON)
- req.responder({ res ->
- res.code(200)
- res.body('{"status":"ok"}', ContentType.APPLICATION_JSON)
- })
- })
- })
-
- and: 'a Micronaut HTTP client targeting the ersatz server'
- def client = HttpClient.create('http://localhost:19876'.toURL())
-
- when: 'calling the endpoint with an Accept header'
- def response = client.toBlocking().exchange(
- HttpRequest.GET('/micronaut-test').accept(MediaType.APPLICATION_JSON),
- String
- )
-
- then: 'the response contains the expected JSON'
- response.status.code == 200
- response.body() == '{"status":"ok"}'
-
- and: 'the ersatz server received exactly one request'
- ersatz.verify()
-
- cleanup:
- client.close()
- }
}
diff --git a/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautErsatzAdvancedSpec.groovy b/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautErsatzAdvancedSpec.groovy
index 21c1c760cc..4ebd4dc6b2 100644
--- a/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautErsatzAdvancedSpec.groovy
+++ b/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautErsatzAdvancedSpec.groovy
@@ -20,9 +20,9 @@ package micronaut
import io.github.cjstehno.ersatz.ErsatzServer
import io.github.cjstehno.ersatz.cfg.ContentType
+import io.github.cjstehno.ersatz.cfg.ServerConfig
import io.micronaut.context.ApplicationContext as MicronautApplicationContext
import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.exceptions.HttpClientResponseException
@@ -31,29 +31,20 @@ import micronaut.client.MicronautHeaderClient
import micronaut.client.MicronautTestClient
import spock.lang.AutoCleanup
import spock.lang.Specification
-import spock.util.concurrent.PollingConditions
import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.beans.factory.annotation.Value
-
-import java.util.concurrent.CopyOnWriteArrayList
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
@Integration
-class MicronautErsatzAdvancedSpec extends Specification {
-
- @Autowired
- MicronautApplicationContext micronautContext
-
- @Autowired
- ExternalApiService externalApiService
+class MicronautErsatzAdvancedSpec extends Specification implements HttpClientSupport {
- @Value('${local.server.port}')
- Integer serverPort
+ @Autowired MicronautApplicationContext micronautContext
+ @Autowired ExternalApiService externalApiService
@AutoCleanup
- ErsatzServer ersatz = new ErsatzServer({ cfg ->
+ ErsatzServer ersatz = new ErsatzServer({ ServerConfig cfg ->
cfg.httpPort(19876)
})
@@ -100,7 +91,7 @@ class MicronautErsatzAdvancedSpec extends Specification {
def client = micronautContext.getBean(MicronautAdvancedClient)
when: 'calling the head endpoint'
- HttpResponse<?> response = client.headCheck()
+ def response = client.headCheck()
then: 'the response contains the header'
response.status.code == 200
@@ -126,7 +117,7 @@ class MicronautErsatzAdvancedSpec extends Specification {
def client = micronautContext.getBean(MicronautAdvancedClient)
when: 'calling the options endpoint'
- HttpResponse<?> response = client.optionsCheck()
+ def response = client.optionsCheck()
then: 'the response contains the allow header'
response.status.code == 200
@@ -136,37 +127,6 @@ class MicronautErsatzAdvancedSpec extends Specification {
ersatz.verify()
}
- void "ANY method matcher in ersatz matches GET request from Micronaut HttpClient"() {
- given: 'ersatz mocks the ANY endpoint'
- ersatz.expectations({ expect ->
- expect.ANY('/micronaut-test/anything', { req ->
- req.called(1)
- req.responder({ res ->
- res.code(200)
- res.body('{"status":"any"}', ContentType.APPLICATION_JSON)
- })
- })
- })
-
- and: 'a low-level Micronaut HTTP client'
- def httpClient = HttpClient.create('http://localhost:19876'.toURL())
-
- when: 'sending a GET request'
- def response = httpClient.toBlocking().exchange(
- HttpRequest.GET('/micronaut-test/anything').accept(MediaType.APPLICATION_JSON),
- String
- )
-
- then: 'the response is successful'
- response.status.code == 200
-
- and: 'ersatz verifies the call'
- ersatz.verify()
-
- cleanup:
- httpClient.close()
- }
-
void "declarative client sends query parameters matched by ersatz"() {
given: 'ersatz expects query parameters'
ersatz.expectations({ expect ->
@@ -209,11 +169,8 @@ class MicronautErsatzAdvancedSpec extends Specification {
})
})
- and: 'a Micronaut HTTP client targeting Grails'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'calling the Grails controller endpoint'
- def response = httpClient.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/external-api/search?q=grails&page=2').accept(MediaType.APPLICATION_JSON),
String
)
@@ -224,9 +181,6 @@ class MicronautErsatzAdvancedSpec extends Specification {
and: 'ersatz verifies the call'
ersatz.verify()
-
- cleanup:
- httpClient.close()
}
void "declarative client sends Authorization header matched by ersatz"() {
@@ -310,41 +264,6 @@ class MicronautErsatzAdvancedSpec extends Specification {
ersatz.verify()
}
- void "ersatz matches Basic Authentication header from Micronaut HttpClient"() {
- given: 'ersatz expects a basic auth header'
- ersatz.expectations({ expect ->
- expect.GET('/micronaut-test/basic-auth', { req ->
- req.header('Authorization', 'Basic dXNlcjpwYXNz')
- req.called(1)
- req.responder({ res ->
- res.code(200)
- res.body('{"auth":"basic"}', ContentType.APPLICATION_JSON)
- })
- })
- })
-
- and: 'a low-level Micronaut HTTP client'
- def httpClient = HttpClient.create('http://localhost:19876'.toURL())
-
- when: 'sending a basic auth request'
- def response = httpClient.toBlocking().exchange(
- HttpRequest.GET('/micronaut-test/basic-auth')
- .basicAuth('user', 'pass')
- .accept(MediaType.APPLICATION_JSON),
- String
- )
-
- then: 'the response is successful'
- response.status.code == 200
- response.body().contains('basic')
-
- and: 'ersatz verifies the call'
- ersatz.verify()
-
- cleanup:
- httpClient.close()
- }
-
void "full roundtrip: Authorization header passes through Grails stack to ersatz"() {
given: 'ersatz expects an authorization header'
ersatz.expectations({ expect ->
@@ -358,11 +277,8 @@ class MicronautErsatzAdvancedSpec extends Specification {
})
})
- and: 'a Micronaut HTTP client targeting Grails'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'calling the Grails controller endpoint'
- def response = httpClient.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/external-api/secure')
.header('Authorization', 'Bearer roundtrip-token')
.accept(MediaType.APPLICATION_JSON),
@@ -375,9 +291,6 @@ class MicronautErsatzAdvancedSpec extends Specification {
and: 'ersatz verifies the call'
ersatz.verify()
-
- cleanup:
- httpClient.close()
}
void "declarative client sends cookie matched by ersatz"() {
@@ -407,39 +320,6 @@ class MicronautErsatzAdvancedSpec extends Specification {
ersatz.verify()
}
- void "ersatz returns response cookies received by Micronaut HttpClient"() {
- given: 'ersatz responds with a cookie'
- ersatz.expectations({ expect ->
- expect.GET('/micronaut-test/cookie-response', { req ->
- req.called(1)
- req.responder({ res ->
- res.code(200)
- res.body('{"cookie":"response"}', ContentType.APPLICATION_JSON)
- res.cookie('tracker', 'visit-42')
- })
- })
- })
-
- and: 'a low-level Micronaut HTTP client'
- def httpClient = HttpClient.create('http://localhost:19876'.toURL())
-
- when: 'making the request'
- def response = httpClient.toBlocking().exchange(
- HttpRequest.GET('/micronaut-test/cookie-response').accept(MediaType.APPLICATION_JSON),
- String
- )
-
- then: 'the set-cookie header is present'
- response.status.code == 200
- response.header('Set-Cookie').contains('tracker')
-
- and: 'ersatz verifies the call'
- ersatz.verify()
-
- cleanup:
- httpClient.close()
- }
-
void "ersatz returns plain text response consumed by declarative client"() {
given: 'ersatz returns plain text'
ersatz.expectations({ expect ->
@@ -465,74 +345,6 @@ class MicronautErsatzAdvancedSpec extends Specification {
ersatz.verify()
}
- void "ersatz returns XML response consumed by Micronaut HttpClient"() {
- given: 'ersatz returns XML'
- ersatz.expectations({ expect ->
- expect.GET('/micronaut-test/xml', { req ->
- req.called(1)
- req.responder({ res ->
- res.code(200)
- res.body('<root><message>hello</message></root>', ContentType.APPLICATION_XML)
- })
- })
- })
-
- and: 'a low-level Micronaut HTTP client'
- def httpClient = HttpClient.create('http://localhost:19876'.toURL())
-
- when: 'requesting XML'
- def response = httpClient.toBlocking().exchange(
- HttpRequest.GET('/micronaut-test/xml')
- .accept(MediaType.APPLICATION_XML),
- String
- )
-
- then: 'the XML response is received'
- response.status.code == 200
- response.body().contains('<message>hello</message>')
-
- and: 'ersatz verifies the call'
- ersatz.verify()
-
- cleanup:
- httpClient.close()
- }
-
- void "ersatz returns delayed response received by Micronaut HttpClient"() {
- given: 'ersatz delays the response'
- ersatz.expectations({ expect ->
- expect.GET('/micronaut-test/slow', { req ->
- req.called(1)
- req.responder({ res ->
- res.code(200)
- res.delay(200)
- res.body('{"status":"slow"}', ContentType.APPLICATION_JSON)
- })
- })
- })
-
- and: 'a low-level Micronaut HTTP client'
- def httpClient = HttpClient.create('http://localhost:19876'.toURL())
-
- when: 'calling the slow endpoint'
- long startTime = System.currentTimeMillis()
- def response = httpClient.toBlocking().exchange(
- HttpRequest.GET('/micronaut-test/slow').accept(MediaType.APPLICATION_JSON),
- String
- )
- long endTime = System.currentTimeMillis()
-
- then: 'the response is delayed and successful'
- response.status.code == 200
- (endTime - startTime) >= 150
-
- and: 'ersatz verifies the call'
- ersatz.verify()
-
- cleanup:
- httpClient.close()
- }
-
void "ersatz verifies exact call count for multiple requests to same endpoint"() {
given: 'ersatz expects multiple calls'
ersatz.expectations({ expect ->
@@ -557,106 +369,6 @@ class MicronautErsatzAdvancedSpec extends Specification {
ersatz.verify()
}
- void "ersatz listener captures request details"() {
- given: 'ersatz captures request details'
- CopyOnWriteArrayList<Object> captured = new CopyOnWriteArrayList<>()
- ersatz.expectations({ expect ->
- expect.GET('/micronaut-test/listen', { req ->
- req.listener({ captured << it })
- req.called(1)
- req.responder({ res ->
- res.code(200)
- res.body('{"listen":"ok"}', ContentType.APPLICATION_JSON)
- })
- })
- })
-
- and: 'a low-level Micronaut HTTP client'
- def httpClient = HttpClient.create('http://localhost:19876'.toURL())
-
- when: 'making the request'
- def response = httpClient.toBlocking().exchange(
- HttpRequest.GET('/micronaut-test/listen').accept(MediaType.APPLICATION_JSON),
- String
- )
-
- then: 'a request was captured'
- response.status.code == 200
-
- and: 'the listener eventually captures the request'
- new PollingConditions(timeout: 5).eventually {
- assert captured.size() == 1
- }
-
- and: 'ersatz verifies the call'
- ersatz.verify()
-
- cleanup:
- httpClient.close()
- }
-
- void "ersatz returns different responses for sequential calls to same endpoint"() {
- given: 'ersatz is configured with sequential responses'
- ersatz.expectations({ expect ->
- expect.GET('/micronaut-test/sequence', { req ->
- req.called(3)
- req.responder({ res ->
- res.code(503)
- res.body('unavailable', ContentType.TEXT_PLAIN)
- })
- req.responder({ res ->
- res.code(503)
- res.body('still unavailable', ContentType.TEXT_PLAIN)
- })
- req.responder({ res ->
- res.code(200)
- res.body('{"status":"ok"}', ContentType.APPLICATION_JSON)
- })
- })
- })
-
- and: 'a low-level Micronaut HTTP client'
- def httpClient = HttpClient.create('http://localhost:19876'.toURL())
-
- when: 'calling the endpoint multiple times'
- def firstException = null
- def secondException = null
- try {
- httpClient.toBlocking().exchange(
- HttpRequest.GET('/micronaut-test/sequence').accept(MediaType.APPLICATION_JSON),
- String
- )
- } catch (HttpClientResponseException ex) {
- firstException = ex
- }
- try {
- httpClient.toBlocking().exchange(
- HttpRequest.GET('/micronaut-test/sequence').accept(MediaType.APPLICATION_JSON),
- String
- )
- } catch (HttpClientResponseException ex) {
- secondException = ex
- }
- def thirdResponse = httpClient.toBlocking().exchange(
- HttpRequest.GET('/micronaut-test/sequence').accept(MediaType.APPLICATION_JSON),
- String
- )
-
- then: 'the first two responses are errors'
- firstException instanceof HttpClientResponseException
- firstException.status.code == 503
- secondException instanceof HttpClientResponseException
- secondException.status.code == 503
- thirdResponse.status.code == 200
- thirdResponse.body().contains('"status":"ok"')
-
- and: 'ersatz verifies the call count'
- ersatz.verify()
-
- cleanup:
- httpClient.close()
- }
-
void "Micronaut client handles 401 Unauthorized from ersatz"() {
given: 'ersatz responds with 401'
ersatz.expectations({ expect ->
@@ -790,11 +502,8 @@ class MicronautErsatzAdvancedSpec extends Specification {
})
})
- and: 'a Micronaut HTTP client targeting Grails'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'sending a PATCH request to Grails'
- def response = httpClient.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.PATCH('/external-api/99', '{"name":"patched"}')
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON),
@@ -807,9 +516,6 @@ class MicronautErsatzAdvancedSpec extends Specification {
and: 'ersatz verifies the call'
ersatz.verify()
-
- cleanup:
- httpClient.close()
}
void "full roundtrip: search with query params through Grails controller to ersatz"() {
@@ -826,11 +532,8 @@ class MicronautErsatzAdvancedSpec extends Specification {
})
})
- and: 'a Micronaut HTTP client targeting Grails'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'calling the search endpoint'
- def response = httpClient.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/external-api/search?q=test-query&page=3').accept(MediaType.APPLICATION_JSON),
String
)
@@ -841,9 +544,6 @@ class MicronautErsatzAdvancedSpec extends Specification {
and: 'ersatz verifies the call'
ersatz.verify()
-
- cleanup:
- httpClient.close()
}
void "Grails service error handling wraps Micronaut client error from ersatz"() {
diff --git a/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautErsatzPatternSpec.groovy b/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautErsatzPatternSpec.groovy
index 723d5546c7..a0d518bb56 100644
--- a/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautErsatzPatternSpec.groovy
+++ b/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautErsatzPatternSpec.groovy
@@ -20,11 +20,10 @@ package micronaut
import io.github.cjstehno.ersatz.ErsatzServer
import io.github.cjstehno.ersatz.cfg.ContentType
+import io.github.cjstehno.ersatz.cfg.ServerConfig
import io.micronaut.context.ApplicationContext as MicronautApplicationContext
import io.micronaut.http.HttpRequest
import io.micronaut.http.MediaType
-import io.micronaut.http.client.HttpClient
-import io.micronaut.http.client.exceptions.HttpClientResponseException
import micronaut.client.MicronautFilteredClient
import micronaut.client.MicronautPathClient
import micronaut.client.MicronautReactiveClient
@@ -33,12 +32,12 @@ import spock.lang.AutoCleanup
import spock.lang.Specification
import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.beans.factory.annotation.Value
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
@Integration
-class MicronautErsatzPatternSpec extends Specification {
+class MicronautErsatzPatternSpec extends Specification implements HttpClientSupport {
@Autowired
MicronautApplicationContext micronautContext
@@ -46,11 +45,8 @@ class MicronautErsatzPatternSpec extends Specification {
@Autowired
ExternalApiService externalApiService
- @Value('${local.server.port}')
- Integer serverPort
-
@AutoCleanup
- ErsatzServer ersatz = new ErsatzServer({ cfg ->
+ ErsatzServer ersatz = new ErsatzServer({ ServerConfig cfg ->
cfg.httpPort(19876)
})
@@ -394,11 +390,8 @@ class MicronautErsatzPatternSpec extends Specification {
})
})
- and: 'a Micronaut HTTP client targeting Grails'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'calling the Grails async controller endpoint'
- def response = httpClient.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/external-api/async').accept(MediaType.APPLICATION_JSON),
String
)
@@ -409,9 +402,6 @@ class MicronautErsatzPatternSpec extends Specification {
and: 'ersatz verifies the call'
ersatz.verify()
-
- cleanup:
- httpClient.close()
}
void "full roundtrip: path client through Grails controller to ersatz"() {
@@ -426,11 +416,8 @@ class MicronautErsatzPatternSpec extends Specification {
})
})
- and: 'a Micronaut HTTP client targeting Grails'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'calling the Grails path controller endpoint'
- def response = httpClient.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/external-api/path/88').accept(MediaType.APPLICATION_JSON),
String
)
@@ -441,9 +428,6 @@ class MicronautErsatzPatternSpec extends Specification {
and: 'ersatz verifies the call'
ersatz.verify()
-
- cleanup:
- httpClient.close()
}
void "full roundtrip: filtered client through Grails controller to ersatz with auto-injected header"() {
@@ -459,11 +443,8 @@ class MicronautErsatzPatternSpec extends Specification {
})
})
- and: 'a Micronaut HTTP client targeting Grails'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'calling the Grails filtered controller endpoint'
- def response = httpClient.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/external-api/filtered').accept(MediaType.APPLICATION_JSON),
String
)
@@ -474,8 +455,5 @@ class MicronautErsatzPatternSpec extends Specification {
and: 'ersatz verifies the header was auto-injected'
ersatz.verify()
-
- cleanup:
- httpClient.close()
}
}
diff --git a/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautErsatzRoundtripSpec.groovy b/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautErsatzRoundtripSpec.groovy
index a5e2afffd5..18c17a7930 100644
--- a/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautErsatzRoundtripSpec.groovy
+++ b/grails-test-examples/micronaut/src/integration-test/groovy/micronaut/MicronautErsatzRoundtripSpec.groovy
@@ -20,29 +20,28 @@ package micronaut
import io.github.cjstehno.ersatz.ErsatzServer
import io.github.cjstehno.ersatz.cfg.ContentType
+import io.github.cjstehno.ersatz.cfg.ServerConfig
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.client.HttpClient
+import io.micronaut.http.client.exceptions.HttpClientResponseException
import spock.lang.AutoCleanup
import spock.lang.Specification
import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.beans.factory.annotation.Value
import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
@Integration
-class MicronautErsatzRoundtripSpec extends Specification {
+class MicronautErsatzRoundtripSpec extends Specification implements HttpClientSupport {
@Autowired
ExternalApiService externalApiService
- @Value('${local.server.port}')
- Integer serverPort
-
@AutoCleanup
- ErsatzServer ersatz = new ErsatzServer({ cfg ->
+ ErsatzServer ersatz = new ErsatzServer({ ServerConfig cfg ->
cfg.httpPort(19876)
})
@@ -223,11 +222,8 @@ class MicronautErsatzRoundtripSpec extends Specification {
})
})
- and: 'an HTTP client targeting the running Grails application'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'hitting the Grails controller endpoint'
- def response = httpClient.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/external-api').accept(MediaType.APPLICATION_JSON),
String
)
@@ -239,9 +235,6 @@ class MicronautErsatzRoundtripSpec extends Specification {
and: 'ersatz confirms it served the mocked response'
ersatz.verify()
-
- cleanup:
- httpClient.close()
}
void "full roundtrip: GET /external-api/{id} returns specific resource from ersatz mock"() {
@@ -256,11 +249,8 @@ class MicronautErsatzRoundtripSpec extends Specification {
})
})
- and: 'an HTTP client targeting the running Grails application'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'hitting the Grails controller show endpoint'
- def response = httpClient.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.GET('/external-api/42').accept(MediaType.APPLICATION_JSON),
String
)
@@ -271,9 +261,6 @@ class MicronautErsatzRoundtripSpec extends Specification {
and: 'ersatz confirms it served the mocked response'
ersatz.verify()
-
- cleanup:
- httpClient.close()
}
void "full roundtrip: POST /external-api creates resource via ersatz mock"() {
@@ -288,11 +275,8 @@ class MicronautErsatzRoundtripSpec extends Specification {
})
})
- and: 'an HTTP client targeting the running Grails application'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'POSTing to the Grails controller'
- def response = httpClient.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.POST('/external-api', '{"name":"test"}')
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON),
@@ -305,9 +289,6 @@ class MicronautErsatzRoundtripSpec extends Specification {
and: 'ersatz confirms it received the POST'
ersatz.verify()
-
- cleanup:
- httpClient.close()
}
void "full roundtrip: PUT /external-api/{id} updates resource via ersatz mock"() {
@@ -322,11 +303,8 @@ class MicronautErsatzRoundtripSpec extends Specification {
})
})
- and: 'an HTTP client targeting the running Grails application'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'PUTting to the Grails controller'
- def response = httpClient.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.PUT('/external-api/42', '{"name":"updated"}')
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON),
@@ -339,9 +317,6 @@ class MicronautErsatzRoundtripSpec extends Specification {
and: 'ersatz confirms it received the PUT'
ersatz.verify()
-
- cleanup:
- httpClient.close()
}
void "full roundtrip: DELETE /external-api/{id} deletes resource via ersatz mock"() {
@@ -355,11 +330,8 @@ class MicronautErsatzRoundtripSpec extends Specification {
})
})
- and: 'an HTTP client targeting the running Grails application'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'DELETEing via the Grails controller'
- def response = httpClient.toBlocking().exchange(
+ def response = httpClient.exchange(
HttpRequest.DELETE('/external-api/42').accept(MediaType.APPLICATION_JSON),
String
)
@@ -369,9 +341,6 @@ class MicronautErsatzRoundtripSpec extends Specification {
and: 'ersatz confirms it received the DELETE'
ersatz.verify()
-
- cleanup:
- httpClient.close()
}
void "full roundtrip: error from ersatz propagates through Grails controller"() {
@@ -386,18 +355,15 @@ class MicronautErsatzRoundtripSpec extends Specification {
})
})
- and: 'an HTTP client targeting the running Grails application'
- def httpClient = HttpClient.create("http://localhost:$serverPort".toURL())
-
when: 'hitting the error-handling endpoint that catches upstream errors'
- io.micronaut.http.client.exceptions.HttpClientResponseException caught = null
- io.micronaut.http.HttpResponse<String> response = null
+ HttpClientResponseException caught = null
+ HttpResponse<String> response = null
try {
- response = httpClient.toBlocking().exchange(
+ response = httpClient.exchange(
HttpRequest.GET('/external-api/safe/fail').accept(MediaType.APPLICATION_JSON),
String
)
- } catch (io.micronaut.http.client.exceptions.HttpClientResponseException ex) {
+ } catch (HttpClientResponseException ex) {
caught = ex
}
@@ -466,42 +432,6 @@ class MicronautErsatzRoundtripSpec extends Specification {
ersatz.verify()
}
- void "full roundtrip: ersatz mocks response with custom headers"() {
- given: 'ersatz mocks an endpoint with custom response headers'
- ersatz.expectations({ expect ->
- expect.GET('/micronaut-test', { req ->
- req.called(1)
- req.responder({ res ->
- res.code(200)
- res.header('X-Custom-Header', 'ersatz-value')
- res.header('X-Request-Id', 'abc-123')
- res.body('{"headers":"present"}', ContentType.APPLICATION_JSON)
- })
- })
- })
-
- and: 'a low-level Micronaut HttpClient targeting ersatz directly'
- def httpClient = HttpClient.create('http://localhost:19876'.toURL())
-
- when: 'making a request to the ersatz server'
- HttpResponse<String> response = httpClient.toBlocking().exchange(
- HttpRequest.GET('/micronaut-test').accept(MediaType.APPLICATION_JSON),
- String
- )
-
- then: 'the custom headers from ersatz are present in the response'
- response.status.code == 200
- response.header('X-Custom-Header') == 'ersatz-value'
- response.header('X-Request-Id') == 'abc-123'
- response.body() == '{"headers":"present"}'
-
- and: 'ersatz received the request'
- ersatz.verify()
-
- cleanup:
- httpClient.close()
- }
-
void "full roundtrip: ersatz mocks large JSON response"() {
given: 'ersatz mocks an endpoint returning a large JSON array'
def largeArray = (1..50).collect { """{"id":"$it","name":"item-$it"}""" }.join(',')
diff --git a/grails-test-examples/views-functional-tests/build.gradle b/grails-test-examples/views-functional-tests/build.gradle
index 369919c05c..aebe95ff75 100644
--- a/grails-test-examples/views-functional-tests/build.gradle
+++ b/grails-test-examples/views-functional-tests/build.gradle
@@ -73,8 +73,7 @@ dependencies {
testImplementation 'org.apache.grails.testing:grails-testing-support-core'
integrationTestImplementation 'com.fasterxml.jackson.core:jackson-databind'
- integrationTestImplementation "io.micronaut:micronaut-http-client:$micronautHttpClientVersion"
- integrationTestImplementation "io.micronaut:micronaut-jackson-databind:$micronautHttpClientVersion"
+ integrationTestImplementation 'org.apache.grails:grails-testing-support-httpclient'
}
apply {
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/BookSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/BookSpec.groovy
index a4f05aa774..5cdfde88c7 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/BookSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/BookSpec.groovy
@@ -20,40 +20,34 @@
package functional.tests
import com.fasterxml.jackson.databind.ObjectMapper
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import grails.web.http.HttpHeaders
import io.micronaut.core.type.Argument
import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.exceptions.HttpClientResponseException
-import org.junit.jupiter.api.BeforeEach
-import spock.lang.Shared
+import spock.lang.Specification
-@Integration(applicationClass = Application)
-class BookSpec extends HttpClientSpec {
+import org.springframework.beans.factory.annotation.Autowired
- @Shared
- ObjectMapper objectMapper
+import grails.testing.mixin.integration.Integration
+import grails.web.http.HttpHeaders
+import org.apache.grails.testing.httpclient.HttpClientSupport
- def setupSpec() {
- objectMapper = new ObjectMapper()
- }
+@Integration
+class BookSpec extends Specification implements HttpClientSupport {
+
+ @Autowired
+ ObjectMapper objectMapper
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
-
void 'Test errors view rendering'() {
when: 'A POST is issued with a missing title'
- HttpRequest request = HttpRequest.POST('/books', [title: ''])
- client.toBlocking().exchange(request, Argument.of(String), Argument.of(String))
+ httpClient.exchange(
+ HttpRequest.POST('/books', [title: '']),
+ Argument.of(String),
+ Argument.of(String)
+ )
then: 'The proper error is returned'
- HttpClientResponseException e = thrown()
+ def e = thrown(HttpClientResponseException)
e.response.status == HttpStatus.UNPROCESSABLE_ENTITY
e.response.headers.getFirst(HttpHeaders.CONTENT_TYPE).isPresent()
e.response.headers.getFirst(HttpHeaders.CONTENT_TYPE).get() == 'application/vnd.error;charset=UTF-8'
@@ -63,7 +57,7 @@ class BookSpec extends HttpClientSpec {
"path": "/book/index",
"_links": {
"self": {
- "href": "$baseUrl/book/index"
+ "href": "$httpClientURL/book/index"
}
}
}""")
@@ -71,8 +65,7 @@ class BookSpec extends HttpClientSpec {
void 'Test REST view rendering'() {
when: 'A GET is issued to get all books'
- HttpRequest request = HttpRequest.GET('/books')
- def resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/books', String)
then: 'The response is correct'
resp.status == HttpStatus.OK
@@ -81,8 +74,8 @@ class BookSpec extends HttpClientSpec {
resp.body() == '[]'
when: 'A POST is issued to create a new book'
- request = HttpRequest.POST('/books', new SaveBookVM(title: 'The Stand'))
- resp = client.toBlocking().exchange(request, Map)
+ def request = HttpRequest.POST('/books', new SaveBookVM(title: 'The Stand'))
+ resp = httpClient.exchange(request, Map)
then: 'The REST resource is created and the correct JSON is returned'
resp.status == HttpStatus.CREATED
@@ -95,8 +88,7 @@ class BookSpec extends HttpClientSpec {
resp.body().vendor == 'MyCompany'
when: 'A GET request is issued'
- request = HttpRequest.GET("/books/${resp.body().id}")
- resp = client.toBlocking().exchange(request, Map)
+ resp = httpClient.exchange("/books/${resp.body().id}", Map)
then: 'The response is correct'
resp.status == HttpStatus.OK
@@ -109,7 +101,13 @@ class BookSpec extends HttpClientSpec {
resp.body().vendor == 'MyCompany'
when: 'A PUT is issued'
- resp = client.toBlocking().exchange(HttpRequest.PUT("/books/${resp.body().id}", new SaveBookVM(title: 'The Changeling')), Map)
+ resp = httpClient.exchange(
+ HttpRequest.PUT(
+ "/books/${resp.body().id}",
+ new SaveBookVM(title: 'The Changeling')
+ ),
+ Map
+ )
then: 'The resource is updated'
resp.status == HttpStatus.OK
@@ -122,8 +120,7 @@ class BookSpec extends HttpClientSpec {
resp.body().vendor == 'MyCompany'
when: 'A GET is issued for all books'
- request = HttpRequest.GET('/books')
- resp = client.toBlocking().exchange(request, String)
+ resp = httpClient.exchange('/books', String)
then: 'The response is correct'
resp.status == HttpStatus.OK
@@ -141,8 +138,7 @@ class BookSpec extends HttpClientSpec {
''')
when: 'A GET is issued for all books with excludes'
- request = HttpRequest.GET('/books/listExcludes?testParam=3')
- resp = client.toBlocking().exchange(request, String)
+ resp = httpClient.exchange('/books/listExcludes?testParam=3', String)
then: 'Access to config and params works'
resp.status == HttpStatus.OK
@@ -161,8 +157,7 @@ class BookSpec extends HttpClientSpec {
''')
when: 'A GET is issued for all books with excludes'
- request = HttpRequest.GET('/books/listExcludesRespond?testParam=4')
- resp = client.toBlocking().exchange(request, String)
+ resp = httpClient.exchange('/books/listExcludesRespond?testParam=4', String)
then: 'view rendering works with a map with respond'
resp.status == HttpStatus.OK
@@ -180,9 +175,7 @@ class BookSpec extends HttpClientSpec {
''')
when: 'A GET is issued for a specific book rendered by a template'
- request = HttpRequest.GET('/books/showWithParams/1?expand=foo')
- resp = client.toBlocking().exchange(request, Map)
-
+ resp = httpClient.exchange('/books/showWithParams/1?expand=foo', Map)
then: 'view rendering with template passes parameters'
resp.status == HttpStatus.OK
@@ -194,8 +187,7 @@ class BookSpec extends HttpClientSpec {
void 'View parameter passed to the render method can be used for non-standard view locations'() {
when: 'A GET is issued to a request with a template at a non-standard location'
- HttpRequest request = HttpRequest.GET('/books/non-standard-template')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/books/non-standard-template', String)
then: 'The template was rendered successfully. The custom converter was also used'
resp.status == HttpStatus.OK
@@ -211,8 +203,7 @@ class BookSpec extends HttpClientSpec {
void 'Object type of list is used for model variable when rendering templates'() {
when:
- HttpRequest request = HttpRequest.GET('/books/listCallsTmpl')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/books/listCallsTmpl', String)
then: 'The template was rendered successfully'
resp.status == HttpStatus.OK
@@ -223,8 +214,7 @@ class BookSpec extends HttpClientSpec {
void 'Object type of list is used for model variable in addition to specified model when rendering templates'() {
when:
- HttpRequest request = HttpRequest.GET("/books/listCallsTmplExtraData")
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/books/listCallsTmplExtraData', String)
then: 'The template was rendered successfully'
resp.status == HttpStatus.OK
@@ -242,8 +232,7 @@ class BookSpec extends HttpClientSpec {
void 'Object type of list is used for model variable in addition to specified model and var when rendering templates'() {
when:
- HttpRequest request = HttpRequest.GET('/books/listCallsTmplVar')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/books/listCallsTmplVar', String)
then: 'The template was rendered successfully'
resp.status == HttpStatus.OK
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/BulletinSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/BulletinSpec.groovy
index 531f0b7eda..12f70655a2 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/BulletinSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/BulletinSpec.groovy
@@ -19,29 +19,21 @@
package functional.tests
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import org.junit.jupiter.api.BeforeEach
import spock.lang.Issue
+import spock.lang.Specification
-@Integration(applicationClass = Application)
-class BulletinSpec extends HttpClientSpec {
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+@Integration
+class BulletinSpec extends Specification implements HttpClientSupport {
@Issue('https://github.com/apache/grails-views/issues/175')
void 'test render collections with same objects'() {
when: 'a GET is issued'
- HttpRequest request = HttpRequest.GET("/bulletin")
- HttpResponse<Map> resp = client.toBlocking().exchange(request, Map)
- Map json = resp.body()
+ def resp = httpClient.exchange('/bulletin', Map)
+ def json = resp.body()
then: 'The REST resource is retrieved and the correct JSON is returned'
resp.status == HttpStatus.OK
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/CircularSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/CircularSpec.groovy
index 4f5a14235a..2b838717ef 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/CircularSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/CircularSpec.groovy
@@ -16,59 +16,49 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
+import io.micronaut.http.HttpStatus
+import spock.lang.Specification
+
import grails.gorm.transactions.Rollback
import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
import grails.web.http.HttpHeaders
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
-import io.micronaut.http.HttpStatus
-import org.junit.jupiter.api.BeforeEach
+import org.apache.grails.testing.httpclient.HttpClientSupport
-@Integration(applicationClass = Application)
@Rollback
-class CircularSpec extends HttpClientSpec {
-
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+@Integration
+class CircularSpec extends Specification implements HttpClientSupport {
void "test deep rendering of circular domain relationships"() {
- when:"A GET is issued"
- HttpRequest request = HttpRequest.GET("/circular/show/1")
- HttpResponse<Map> resp = client.toBlocking().exchange(request, Map)
+ when: "A GET is issued"
+ def resp = httpClient.exchange('/circular/show/1', Map)
def json = resp.body()
- then:"The REST resource is retrieved and the correct JSON is returned"
+ then: "The REST resource is retrieved and the correct JSON is returned"
resp.status == HttpStatus.OK
resp.headers.getFirst(HttpHeaders.CONTENT_TYPE).isPresent()
resp.headers.getFirst(HttpHeaders.CONTENT_TYPE).get() == 'application/json;charset=UTF-8'
json.id == 1
- json.name == "topLevel"
- json.myEnum == "BAR"
+ json.name == 'topLevel'
+ json.myEnum == 'BAR'
json.circulars.size() == 2
json.circulars.find { it.id == 3 }.parent.id == 1
json.circulars.find { it.id == 2 }.parent.id == 1
}
void "test nested template rendering of circular domain relationships"() {
- when:"A GET is issued"
- HttpRequest request = HttpRequest.GET("/circular/circular/1")
- HttpResponse<Map> resp = client.toBlocking().exchange(request, Map)
+ when: "A GET is issued"
+ def resp = httpClient.exchange('/circular/circular/1', Map)
def json = resp.body()
- then:"The REST resource is retrieved and the correct JSON is returned"
+ then: "The REST resource is retrieved and the correct JSON is returned"
resp.status == HttpStatus.OK
resp.headers.getFirst(HttpHeaders.CONTENT_TYPE).isPresent()
resp.headers.getFirst(HttpHeaders.CONTENT_TYPE).get() == 'application/json;charset=UTF-8'
- json.name == "topLevel"
+ json.name == 'topLevel'
json.children.size() == 2
- json.children.find { it.name == "topLevel-3" }
- json.children.find { it.name == "topLevel-2" }
+ json.children.find { it.name == 'topLevel-3' }
+ json.children.find { it.name == 'topLevel-2' }
}
}
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/CustomerSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/CustomerSpec.groovy
index 3add42acb7..5ab160387c 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/CustomerSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/CustomerSpec.groovy
@@ -16,38 +16,29 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import org.junit.jupiter.api.BeforeEach
+import spock.lang.Specification
-@Integration(applicationClass = Application)
-class CustomerSpec extends HttpClientSpec {
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+@Integration
+class CustomerSpec extends Specification implements HttpClientSupport {
void "Test that circular references are correctly rendered for one to many relationship"() {
when:
- HttpRequest request = HttpRequest.GET("/customer")
- HttpResponse<Map> resp = client.toBlocking().exchange(request, Map)
- Map json = resp.body()
+ def resp = httpClient.exchange('/customer', Map)
+ def json = resp.body()
- then:"The correct response is returned"
+ then: "The correct response is returned"
resp.status == HttpStatus.OK
json.id == 1
- json.name == "Nokia"
- json.sites.find { it.id == 1 }.name == "Salo"
+ json.name == 'Nokia'
+ json.sites.find { it.id == 1 }.name == 'Salo'
json.sites.find { it.id == 1 }.customer == null
- json.sites.find { it.id == 2 }.name == "Helsinki"
+ json.sites.find { it.id == 2 }.name == 'Helsinki'
json.sites.find { it.id == 2 }.customer == null
}
}
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/EmbeddedSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/EmbeddedSpec.groovy
index 5c2f368725..c93634438e 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/EmbeddedSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/EmbeddedSpec.groovy
@@ -16,41 +16,29 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
import com.fasterxml.jackson.databind.ObjectMapper
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import org.junit.jupiter.api.BeforeEach
import spock.lang.Issue
-import spock.lang.Shared
+import spock.lang.Specification
+
+import org.springframework.beans.factory.annotation.Autowired
+
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
import static io.micronaut.http.HttpHeaders.CONTENT_TYPE
@Integration
-class EmbeddedSpec extends HttpClientSpec {
+class EmbeddedSpec extends Specification implements HttpClientSupport {
- @Shared
+ @Autowired
ObjectMapper objectMapper
- def setup() {
- objectMapper = new ObjectMapper()
- }
-
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
-
void 'Test render can handle a domain with an embedded src/groovy class'() {
when:
- HttpRequest request = HttpRequest.GET('/embedded')
- HttpResponse<String> rsp = client.toBlocking().exchange(request, String)
+ def rsp = httpClient.exchange('/embedded', String)
then: 'The response is correct'
rsp.status() == HttpStatus.OK
@@ -72,8 +60,7 @@ class EmbeddedSpec extends HttpClientSpec {
void 'Test jsonapi render can handle a domain with an embedded src/groovy class'() {
when:
- HttpRequest request = HttpRequest.GET('/embedded/jsonapi')
- HttpResponse<String> rsp = client.toBlocking().exchange(request, String)
+ def rsp = httpClient.exchange('/embedded/jsonapi', String)
then: 'The response is correct'
rsp.status() == HttpStatus.OK
@@ -104,8 +91,7 @@ class EmbeddedSpec extends HttpClientSpec {
@Issue('https://github.com/apache/grails-views/issues/171')
void 'test render can handle a domain with an embedded and includes src/groovy class'() {
when:
- HttpRequest request = HttpRequest.GET('/embedded/embeddedWithIncludes')
- HttpResponse<String> rsp = client.toBlocking().exchange(request, String)
+ def rsp = httpClient.exchange('/embedded/embeddedWithIncludes', String)
then: 'the response is correct'
rsp.status() == HttpStatus.OK
@@ -123,8 +109,7 @@ class EmbeddedSpec extends HttpClientSpec {
@Issue('https://github.com/apache/grails-views/issues/171')
void 'Test jsonapi render can handle a domain with an embedded and includes src/groovy class'() {
when:
- HttpRequest request = HttpRequest.GET('/embedded/embeddedWithIncludesJsonapi')
- HttpResponse<String> rsp = client.toBlocking().exchange(request, String)
+ def rsp = httpClient.exchange('/embedded/embeddedWithIncludesJsonapi', String)
then: 'the response is correct'
rsp.status() == HttpStatus.OK
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/HttpClientSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/HttpClientSpec.groovy
deleted file mode 100644
index ebdbed974d..0000000000
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/HttpClientSpec.groovy
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package functional.tests
-
-import grails.testing.spock.RunOnce
-import io.micronaut.http.client.HttpClient
-import org.junit.jupiter.api.BeforeEach
-import spock.lang.AutoCleanup
-import spock.lang.Shared
-import spock.lang.Specification
-
-class HttpClientSpec extends Specification {
-
- @Shared
- @AutoCleanup
- HttpClient client
-
- @Shared
- String baseUrl
-
- @RunOnce
- @BeforeEach
- void init() {
- this.baseUrl = "http://localhost:$serverPort"
- this.client = HttpClient.create(new URL(baseUrl))
- }
-}
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/InheritanceSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/InheritanceSpec.groovy
index 435e236b3c..b073901de8 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/InheritanceSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/InheritanceSpec.groovy
@@ -16,33 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
+import io.micronaut.http.HttpStatus
+import spock.lang.Specification
+
import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
import grails.web.http.HttpHeaders
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
-import io.micronaut.http.HttpStatus
-import org.junit.jupiter.api.BeforeEach
+import org.apache.grails.testing.httpclient.HttpClientSupport
-@Integration(applicationClass = Application)
-class InheritanceSpec extends HttpClientSpec {
+@Integration
+class InheritanceSpec extends Specification implements HttpClientSupport {
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
void "Test template is found for proxy instance that is initialized"() {
when:
- HttpRequest request = HttpRequest.GET('/inheritance/multiNested')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/inheritance/multiNested', String)
- then:"The response is correct"
+ then: "The response is correct"
resp.status == HttpStatus.OK
resp.headers.getFirst(HttpHeaders.CONTENT_TYPE).isPresent()
resp.headers.getFirst(HttpHeaders.CONTENT_TYPE).get() == 'application/json;charset=UTF-8'
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ModelInterceptorIntSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ModelInterceptorIntSpec.groovy
index 34a45f09c8..d2b606ef1b 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ModelInterceptorIntSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ModelInterceptorIntSpec.groovy
@@ -16,27 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
-import org.junit.jupiter.api.BeforeEach
-import org.springframework.beans.factory.annotation.Autowired
+import spock.lang.Specification
import spock.lang.Unroll
-@Integration(applicationClass = Application)
-class ModelInterceptorIntSpec extends HttpClientSpec {
+import org.springframework.beans.factory.annotation.Autowired
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
+
+@Integration
+class ModelInterceptorIntSpec extends Specification implements HttpClientSupport {
@Autowired
ModelInterceptor modelInterceptor
@@ -44,17 +38,20 @@ class ModelInterceptorIntSpec extends HttpClientSpec {
@Unroll
void "interceptor should get model from #controller"() {
given:
- def request = HttpRequest.GET("/$controller")
- .contentType(MediaType.APPLICATION_JSON)
- HttpResponse response = client.toBlocking().exchange(request, List<Map>)
+ def response = httpClient.exchange(
+ HttpRequest.GET("/$controller").contentType(MediaType.APPLICATION_JSON),
+ List<Map>
+ )
expect:
response.status == HttpStatus.OK
modelInterceptor.latestModel != null
where:
- controller << ["modelAndView",
- "respond",
- "return"]
+ controller << [
+ 'modelAndView',
+ 'respond',
+ 'return'
+ ]
}
}
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ObjectTemplateSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ObjectTemplateSpec.groovy
index 9e565f4f03..6daec85f3c 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ObjectTemplateSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ObjectTemplateSpec.groovy
@@ -16,31 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import org.junit.jupiter.api.BeforeEach
+import spock.lang.Specification
-@Integration
-class ObjectTemplateSpec extends HttpClientSpec {
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+@Integration
+class ObjectTemplateSpec extends Specification implements HttpClientSupport {
void "Test that if there is a global /object/_object template it is rendered if no template found"() {
- when:"A POST is issued"
- HttpRequest request = HttpRequest.GET('/place/test')
- HttpResponse<String> rsp = client.toBlocking().exchange(request, String)
+ when: "A POST is issued"
+ def rsp = httpClient.exchange('/place/test', String)
- then:"The correct response is returned"
+ then: "The correct response is returned"
rsp.status() == HttpStatus.OK
rsp.body() == '{"location":"UK","name":"London"}'
}
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/PersonInheritanceSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/PersonInheritanceSpec.groovy
index 5e09cd6c50..1120a2fc06 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/PersonInheritanceSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/PersonInheritanceSpec.groovy
@@ -16,30 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import org.junit.jupiter.api.BeforeEach
import spock.lang.Issue
+import spock.lang.Specification
-@Integration(applicationClass = Application)
-class PersonInheritanceSpec extends HttpClientSpec {
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+@Integration
+class PersonInheritanceSpec extends Specification implements HttpClientSupport {
void 'test template inheritance produces correct json'() {
when:
- HttpRequest request = HttpRequest.GET('/person-inheritance')
- HttpResponse<String> rsp = client.toBlocking().exchange(request, String)
+ def rsp = httpClient.exchange('/person-inheritance', String)
then: 'the response is correct'
rsp.status() == HttpStatus.OK
@@ -49,8 +40,7 @@ class PersonInheritanceSpec extends HttpClientSpec {
@Issue("https://github.com/apache/grails-views/issues/234")
void 'test template inheritance does not produce NPE when model variable is null'() {
when:
- HttpRequest request = HttpRequest.GET('/person-inheritance/npe')
- HttpResponse<String> rsp = client.toBlocking().exchange(request, String)
+ def rsp = httpClient.exchange('/person-inheritance/npe', String)
then: 'the response is correct'
noExceptionThrown()
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ProductSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ProductSpec.groovy
index 98ad33c265..4850ef3c97 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ProductSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ProductSpec.groovy
@@ -16,40 +16,29 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
import com.fasterxml.jackson.databind.ObjectMapper
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import grails.web.http.HttpHeaders
import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import org.junit.jupiter.api.BeforeEach
-import spock.lang.Shared
+import spock.lang.Specification
-@Integration(applicationClass = Application)
-class ProductSpec extends HttpClientSpec {
+import org.springframework.beans.factory.annotation.Autowired
- @Shared
- ObjectMapper objectMapper
+import grails.testing.mixin.integration.Integration
+import grails.web.http.HttpHeaders
+import org.apache.grails.testing.httpclient.HttpClientSupport
- def setup() {
- objectMapper = new ObjectMapper()
- }
+@Integration
+class ProductSpec extends Specification implements HttpClientSupport {
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+ @Autowired
+ ObjectMapper objectMapper
void testEmptyProducts() {
when:
- HttpRequest request = HttpRequest.GET('/products')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
- Map body = objectMapper.readValue(resp.body(), Map)
+ def resp = httpClient.exchange('/products', String)
+ def body = objectMapper.readValue(resp.body(), Map)
then:
resp.status == HttpStatus.OK
@@ -65,7 +54,7 @@ class ProductSpec extends HttpClientSpec {
and: 'the hal _links attribute is present'
body._links.size() == 1
- body._links.self.href.startsWith("${baseUrl}/product")
+ body._links.self.href.startsWith("$httpClientURL/product")
and: 'there are no products yet'
body._embedded.products.size() == 0
@@ -73,23 +62,22 @@ class ProductSpec extends HttpClientSpec {
void testSingleProduct() {
given:
- HttpRequest request = HttpRequest.POST('/products', [
+ def request = HttpRequest.POST('/products', [
name: 'Product 1',
description: 'product 1 description',
price: 123.45
])
when:
- HttpResponse<String> createResp = client.toBlocking().exchange(request, String)
- Map createBody = objectMapper.readValue(createResp.body(), Map)
+ def createResp = httpClient.exchange(request, String)
+ def createBody = objectMapper.readValue(createResp.body(), Map)
then:
createResp.status == HttpStatus.CREATED
when: 'We get the products'
- request = HttpRequest.GET('/products')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
- Map body = objectMapper.readValue(resp.body(), Map)
+ def resp = httpClient.exchange('/products', String)
+ def body = objectMapper.readValue(resp.body(), Map)
then:
resp.status == HttpStatus.OK
@@ -105,14 +93,14 @@ class ProductSpec extends HttpClientSpec {
and: 'the hal _links attribute is present'
body._links.size() == 1
- body._links.self.href.startsWith("${baseUrl}/product")
+ body._links.self.href.startsWith("$httpClientURL/product")
and: 'the product is present'
body._embedded.products.size() == 1
body._embedded.products.first().name == 'Product 1'
cleanup:
- resp = client.toBlocking().exchange(HttpRequest.DELETE("/products/${createBody.id}"))
+ resp = httpClient.exchange(HttpRequest.DELETE("/products/${createBody.id}"))
assert resp.status() == HttpStatus.OK
}
@@ -120,22 +108,21 @@ class ProductSpec extends HttpClientSpec {
given:
def productsIds = []
15.times { productNumber ->
- ProductVM product = new ProductVM(
+ def product = new ProductVM(
name: "Product $productNumber",
description: "product ${productNumber} description",
price: productNumber + (productNumber / 100)
)
- HttpResponse<String> createResp = client.toBlocking()
+ def createResp = httpClient
.exchange(HttpRequest.POST('/products', product), String)
- Map createBody = objectMapper.readValue(createResp.body(), Map)
+ def createBody = objectMapper.readValue(createResp.body(), Map)
assert createResp.status == HttpStatus.CREATED
productsIds << createBody.id
}
when: 'We get the products'
- HttpRequest request = HttpRequest.GET('/products')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
- Map body = objectMapper.readValue(resp.body(), Map)
+ def resp = httpClient.exchange('/products', String)
+ def body = objectMapper.readValue(resp.body(), Map)
then:
resp.status == HttpStatus.OK
@@ -151,18 +138,18 @@ class ProductSpec extends HttpClientSpec {
and: 'the hal _links attribute is present'
body._links.size() == 4
- body._links.self.href.startsWith("${baseUrl}/product")
- body._links.first.href.startsWith("${baseUrl}/product")
- body._links.next.href.startsWith("${baseUrl}/product")
- body._links.last.href.startsWith("${baseUrl}/product")
+ body._links.self.href.startsWith("$httpClientURL/product")
+ body._links.first.href.startsWith("$httpClientURL/product")
+ body._links.next.href.startsWith("$httpClientURL/product")
+ body._links.last.href.startsWith("$httpClientURL/product")
and: 'the product is present'
body._embedded.products.size() == 10
cleanup:
productsIds.each { id ->
- resp = client.toBlocking().exchange(HttpRequest.DELETE("/products/${id}"))
- assert resp.status() == HttpStatus.OK
+ def r = httpClient.exchange(HttpRequest.DELETE("/products/${id}"))
+ assert r.status() == HttpStatus.OK
}
}
@@ -175,16 +162,18 @@ class ProductSpec extends HttpClientSpec {
description: "product ${productNumber} description",
price: productNumber + (productNumber / 100)
)
- HttpResponse<String> createResp = client.toBlocking().exchange(HttpRequest.POST('/products', product), String)
+ def createResp = httpClient.exchange(
+ HttpRequest.POST('/products', product),
+ String
+ )
assert createResp.status == HttpStatus.CREATED
- Map createBody = objectMapper.readValue(createResp.body(), Map)
+ def createBody = objectMapper.readValue(createResp.body(), Map)
productsIds << createBody.id
}
when: 'We get the products'
- HttpRequest request = HttpRequest.GET('/products?offset=10')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
- Map body = objectMapper.readValue(resp.body(), Map)
+ def resp = httpClient.exchange('/products?offset=10', String)
+ def body = objectMapper.readValue(resp.body(), Map)
then:
resp.status == HttpStatus.OK
@@ -200,19 +189,19 @@ class ProductSpec extends HttpClientSpec {
and: 'the hal _links attribute is present'
body._links.size() == 5
- body._links.self.href.startsWith("${baseUrl}/product")
- body._links.first.href.startsWith("${baseUrl}/product")
- body._links.prev.href.startsWith("${baseUrl}/product")
- body._links.next.href.startsWith("${baseUrl}/product")
- body._links.last.href.startsWith("${baseUrl}/product")
+ body._links.self.href.startsWith("$httpClientURL/product")
+ body._links.first.href.startsWith("$httpClientURL/product")
+ body._links.prev.href.startsWith("$httpClientURL/product")
+ body._links.next.href.startsWith("$httpClientURL/product")
+ body._links.last.href.startsWith("$httpClientURL/product")
and: 'the product is present'
body._embedded.products.size() == 10
cleanup:
productsIds.each { id ->
- resp = client.toBlocking().exchange(HttpRequest.DELETE("/products/${id}"))
- assert resp.status() == HttpStatus.OK
+ def r = httpClient.exchange(HttpRequest.DELETE("/products/${id}"))
+ assert r.status() == HttpStatus.OK
}
}
}
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ProjectSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ProjectSpec.groovy
index 1f2e453938..f6d1107c76 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ProjectSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ProjectSpec.groovy
@@ -16,44 +16,33 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import org.junit.jupiter.api.BeforeEach
+import spock.lang.Specification
-@Integration(applicationClass = Application)
-class ProjectSpec extends HttpClientSpec {
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+@Integration
+class ProjectSpec extends Specification implements HttpClientSupport {
void "Test that circular references are correctly rendered for many to many relationship"() {
- given:"A rest client"
-
- when:"A POST is issued"
- HttpRequest request = HttpRequest.GET('/project')
- HttpResponse<Map> rsp = client.toBlocking().exchange(request, Map)
+ when: "A GET is issued"
+ def rsp = httpClient.exchange('/project', Map)
then:
rsp.status() == HttpStatus.OK
when:
- Map project = rsp.body()
+ def project = rsp.body()
- then:"The correct response is returned"
+ then: "The correct response is returned"
project.id == 1
project.name == "Grails Views"
- project.employees.find { it.id == 1 }.name == "James Kleeh"
+ project.employees.find { it.id == 1 }.name == 'James Kleeh'
project.employees.find { it.id == 1 }.project == null
- project.employees.find { it.id == 2 }.name == "Iván López"
+ project.employees.find { it.id == 2 }.name == 'Iván López'
project.employees.find { it.id == 2 }.project == null
}
}
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ProxySpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ProxySpec.groovy
index 91d071adaa..1f3d09268d 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ProxySpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/ProxySpec.groovy
@@ -16,32 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import grails.web.http.HttpHeaders
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import org.junit.jupiter.api.BeforeEach
+import spock.lang.Specification
-@Integration(applicationClass = Application)
-class ProxySpec extends HttpClientSpec {
+import grails.testing.mixin.integration.Integration
+import grails.web.http.HttpHeaders
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+@Integration
+class ProxySpec extends Specification implements HttpClientSupport {
void "Test template is found for proxy instance that is initialized"() {
when:
- HttpRequest request = HttpRequest.GET("/proxy")
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/proxy', String)
- then:"The response is correct"
+ then: "The response is correct"
resp.status == HttpStatus.OK
resp.headers.getFirst(HttpHeaders.CONTENT_TYPE).isPresent()
resp.headers.getFirst(HttpHeaders.CONTENT_TYPE).get() == 'application/json;charset=UTF-8'
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TeamSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TeamSpec.groovy
index 2aedc1ca9e..afb6f23f26 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TeamSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TeamSpec.groovy
@@ -16,47 +16,33 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
import com.fasterxml.jackson.databind.ObjectMapper
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import grails.web.http.HttpHeaders
import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import org.junit.jupiter.api.BeforeEach
import spock.lang.IgnoreIf
import spock.lang.Shared
+import spock.lang.Specification
-@Integration(applicationClass = Application)
-class TeamSpec extends HttpClientSpec {
+import org.springframework.beans.factory.annotation.Autowired
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+import grails.testing.mixin.integration.Integration
+import grails.web.http.HttpHeaders
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @Shared
- String lang
+@Integration
+class TeamSpec extends Specification implements HttpClientSupport {
@Shared
- ObjectMapper objectMapper
-
- void setup() {
- objectMapper = new ObjectMapper()
- }
+ String lang = "${System.properties.getProperty('user.language')}_${System.properties.getProperty('user.country')}"
- void setupSpec() {
- this.lang = "${System.properties.getProperty('user.language')}_${System.properties.getProperty('user.country')}"
- }
+ @Autowired
+ ObjectMapper objectMapper
void 'Test association template rendering'() {
when:
- HttpRequest request = HttpRequest.GET('/teams/1')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/teams/1', String)
then: 'The response is correct'
resp.status == HttpStatus.OK
@@ -80,8 +66,7 @@ class TeamSpec extends HttpClientSpec {
void 'Test deep association template rendering'() {
when:
- HttpRequest request = HttpRequest.GET('/teams/deep/1')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/teams/deep/1', String)
then: 'The response is correct'
resp.status == HttpStatus.OK
@@ -104,8 +89,7 @@ class TeamSpec extends HttpClientSpec {
@IgnoreIf({ System.getenv('GITHUB_REF') })
void 'Test HAL rendering'() {
when:
- HttpRequest request = HttpRequest.GET('/teams/hal/1')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/teams/hal/1', String)
then: 'The response is correct'
resp.status == HttpStatus.OK
@@ -177,8 +161,7 @@ class TeamSpec extends HttpClientSpec {
}
when:
- HttpRequest request = HttpRequest.GET('/team/composite')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/team/composite', String)
then: 'The response is correct'
resp.status == HttpStatus.OK
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TestControllerSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TestControllerSpec.groovy
index 8aea9874d8..af1d39e803 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TestControllerSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TestControllerSpec.groovy
@@ -16,36 +16,26 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import io.micronaut.core.type.Argument
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.exceptions.HttpClientException
-import org.junit.jupiter.api.BeforeEach
import spock.lang.Issue
+import spock.lang.Specification
-@Integration(applicationClass = Application)
-class TestControllerSpec extends HttpClientSpec {
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+@Integration
+class TestControllerSpec extends Specification implements HttpClientSupport {
@Issue('https://github.com/apache/grails-core/issues/10582')
void 'test responding after an action triggered by a HTTP 401 response is possible'() {
when:
- HttpRequest request = HttpRequest.GET("/test/triggerUnauthorized")
- HttpResponse<String> resp = client.toBlocking().exchange(request, Argument.of(String), Argument.of(String))
+ httpClient.retrieve('/test/triggerUnauthorized')
then: 'the response is correct'
- HttpClientException e = thrown()
+ def e = thrown(HttpClientException)
e.response.status == HttpStatus.UNAUTHORIZED
e.response.body() == '{"message":"Unauthorized GSON"}'
}
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TestGmlControllerSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TestGmlControllerSpec.groovy
index 39d9941ed5..d68c44a43a 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TestGmlControllerSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TestGmlControllerSpec.groovy
@@ -16,35 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
import groovy.xml.XmlSlurper
-import groovy.xml.slurpersupport.GPathResult
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
-import org.junit.jupiter.api.BeforeEach
-@Integration(applicationClass = Application)
-class TestGmlControllerSpec extends HttpClientSpec {
+import spock.lang.Specification
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
+
+@Integration
+class TestGmlControllerSpec extends Specification implements HttpClientSupport {
void "Test GML response from action that returns a model"() {
- when:"When an action that renders a GML view is requested"
- String uri = '/testGml/testView'
- HttpRequest request = HttpRequest.GET(uri)
- HttpResponse<String> rsp = client.toBlocking().exchange(request, String)
- GPathResult content = new XmlSlurper().parseText(rsp.body())
+ when: "When an action that renders a GML view is requested"
+ def rsp = httpClient.exchange('/testGml/testView', String)
+ def content = new XmlSlurper().parseText(rsp.body())
- then:"The XML view is rendered"
+ then: "The XML view is rendered"
content.car.size() == 1
- content.car[0].@make.text() == "Audi"
+ content.car[0].@make.text() == 'Audi'
}
}
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TestGsonControllerSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TestGsonControllerSpec.groovy
index 45d3614879..f19e0c7137 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TestGsonControllerSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/TestGsonControllerSpec.groovy
@@ -16,37 +16,26 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
import com.fasterxml.jackson.databind.ObjectMapper
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
-import org.junit.jupiter.api.BeforeEach
-import spock.lang.Shared
+import spock.lang.Specification
+
+import org.springframework.beans.factory.annotation.Autowired
-@Integration(applicationClass = Application)
-class TestGsonControllerSpec extends HttpClientSpec {
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
+
+@Integration
+class TestGsonControllerSpec extends Specification implements HttpClientSupport {
- @Shared
+ @Autowired
ObjectMapper objectMapper
- def setup() {
- objectMapper = new ObjectMapper()
- }
-
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
-
void 'Test that responding with a map is possible'() {
when: 'When JSON is requested'
- HttpRequest request = HttpRequest.GET('/testGson/testRespondWithMap')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/testGson/testRespondWithMap', String)
then: 'The JSON view is rendered'
resp.body() == '{"message":"two"}'
@@ -54,8 +43,7 @@ class TestGsonControllerSpec extends HttpClientSpec {
void 'Test that responding with a map is possible with object template'() {
when: 'When JSON is requested'
- HttpRequest request = HttpRequest.GET('/testGson/testRespondWithMapObjectTemplate.json')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/testGson/testRespondWithMapObjectTemplate.json', String)
then: 'The JSON view is rendered'
resp.body() == '{"one":"two"}'
@@ -64,8 +52,7 @@ class TestGsonControllerSpec extends HttpClientSpec {
void 'Test that it is possible to use the template engine directly'() {
when: 'When JSON is requested'
- HttpRequest request = HttpRequest.GET('/testGson/testTemplateEngine')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/testGson/testTemplateEngine', String)
then: 'The JSON view is rendered'
objectMapper.readTree(resp.body()) == objectMapper.readTree('''
@@ -79,24 +66,30 @@ class TestGsonControllerSpec extends HttpClientSpec {
void 'Test the respond method returns a GSON view for JSON request'() {
when: 'When JSON is requested'
- HttpRequest request = HttpRequest.GET('/testGson/testRespond.json')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange(
+ '/testGson/testRespond.json',
+ String
+ )
- then:'The JSON view is rendered'
+ then: 'The JSON view is rendered'
resp.body() == '{"test":{"name":"Bob"}}'
when: 'When HTML is requested'
- request = HttpRequest.GET('/testGson/testRespond.html')
- resp = client.toBlocking().exchange(request, String)
+ resp = httpClient.exchange(
+ '/testGson/testRespond.html',
+ String
+ )
- then:'The GSP is rendered'
+ then: 'The GSP is rendered'
resp.body().contains('<h1>Test Bob HTML</h1>')
}
void 'Test the respond method returns a GSON named after the domain view for JSON request'() {
when: 'When JSON is requested'
- HttpRequest request = HttpRequest.GET('/testGson/testRespondWithTemplateForDomain.json')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange(
+ '/testGson/testRespondWithTemplateForDomain.json',
+ String
+ )
then: 'The JSON view is rendered'
objectMapper.readTree(resp.body()) == objectMapper.readTree('''
@@ -111,8 +104,10 @@ class TestGsonControllerSpec extends HttpClientSpec {
void 'Test template rendering works'() {
when: 'A view that renders templates is rendered'
- HttpRequest request = HttpRequest.GET('/testGson/testTemplate.json')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange(
+ '/testGson/testTemplate.json',
+ String
+ )
then: 'The result is correct'
objectMapper.readTree(resp.body()) == objectMapper.readTree('''
@@ -140,8 +135,10 @@ class TestGsonControllerSpec extends HttpClientSpec {
void 'Test views from plugins are rendered'() {
when: 'A view that renders templates is rendered'
- HttpRequest request = HttpRequest.GET('/testGson/testGsonFromPlugin')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange(
+ '/testGson/testGsonFromPlugin',
+ String
+ )
then: 'The result is correct'
resp.body() == '{"message":"Hello from Plugin"}'
@@ -149,8 +146,10 @@ class TestGsonControllerSpec extends HttpClientSpec {
void 'Test view that inherits from plugins are rendered'() {
when:
- HttpRequest request = HttpRequest.GET('/testGson/testInheritsFromPlugin')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange(
+ '/testGson/testInheritsFromPlugin',
+ String
+ )
then:
objectMapper.readTree(resp.body()) == objectMapper.readTree('''
@@ -163,8 +162,10 @@ class TestGsonControllerSpec extends HttpClientSpec {
void 'Test augmenting model'() {
when: 'When JSON is requested'
- HttpRequest request = HttpRequest.GET('/testGson/testAugmentModel.json')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange(
+ '/testGson/testAugmentModel.json',
+ String
+ )
then: 'The JSON view is rendered'
objectMapper.readTree(resp.body()) == objectMapper.readTree('''
@@ -177,10 +178,12 @@ class TestGsonControllerSpec extends HttpClientSpec {
''')
when: 'When HTML is requested'
- request = HttpRequest.GET('/testGson/testAugmentModel.html')
- resp = client.toBlocking().exchange(request, String)
+ resp = httpClient.exchange(
+ '/testGson/testAugmentModel.html',
+ String
+ )
then: 'The GSP is rendered'
- resp.body().contains("<h1>Test John (20) HTML</h1>")
+ resp.body().contains('<h1>Test John (20) HTML</h1>')
}
}
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/VehicleSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/VehicleSpec.groovy
index ae37c5da70..93fa4778e5 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/VehicleSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/VehicleSpec.groovy
@@ -16,32 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package functional.tests
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
-import org.junit.jupiter.api.BeforeEach
-import spock.lang.PendingFeature
+import spock.lang.Specification
-@Integration(applicationClass = Application)
-class VehicleSpec extends HttpClientSpec {
+import grails.testing.mixin.integration.Integration
+import org.apache.grails.testing.httpclient.HttpClientSupport
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+@Integration
+class VehicleSpec extends Specification implements HttpClientSupport {
void "Test that domain subclasses render their properties"() {
when:
- HttpRequest request = HttpRequest.GET('/vehicle/list')
- HttpResponse<String> resp = client.toBlocking().exchange(request, String)
+ def resp = httpClient.exchange('/vehicle/list', String)
- then:"The correct response is returned"
+ then: "The correct response is returned"
resp.status == HttpStatus.OK
resp.body() == '[{"id":1,"route":"around town","maxPassengers":30},{"id":2,"make":"Subaru","model":"WRX","year":2016,"maxPassengers":4}]'
@@ -49,20 +39,19 @@ class VehicleSpec extends HttpClientSpec {
void "Test that domain association subclasses render their properties"() {
when:
- HttpRequest request = HttpRequest.GET('/vehicle/garage')
- HttpResponse<Map> resp = client.toBlocking().exchange(request, Map)
+ def resp = httpClient.exchange('/vehicle/garage', Map)
def json = resp.body()
- then:"The correct response is returned"
+ then: "The correct response is returned"
resp.status == HttpStatus.OK
json.id == 1
- json.owner == "Jay Leno"
+ json.owner == 'Jay Leno'
json.vehicles.find { it.id == 1 }.maxPassengers == 30
- json.vehicles.find { it.id == 1 }.route == "around town"
+ json.vehicles.find { it.id == 1 }.route == 'around town'
json.vehicles.find { it.id == 2 }.maxPassengers == 4
- json.vehicles.find { it.id == 2 }.make == "Subaru"
- json.vehicles.find { it.id == 2 }.model == "WRX"
+ json.vehicles.find { it.id == 2 }.make == 'Subaru'
+ json.vehicles.find { it.id == 2 }.model == 'WRX'
json.vehicles.find { it.id == 2 }.year == 2016
}
}
diff --git a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/api/NamespacedBookSpec.groovy b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/api/NamespacedBookSpec.groovy
index cd86d278c5..e7c8b89138 100644
--- a/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/api/NamespacedBookSpec.groovy
+++ b/grails-test-examples/views-functional-tests/src/integration-test/groovy/functional/tests/api/NamespacedBookSpec.groovy
@@ -20,54 +20,41 @@
package functional.tests.api
import com.fasterxml.jackson.databind.ObjectMapper
-import functional.tests.Application
-import functional.tests.HttpClientSpec
-import grails.testing.mixin.integration.Integration
-import grails.testing.spock.RunOnce
-import grails.web.http.HttpHeaders
import io.micronaut.http.HttpRequest
-import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.MediaType
-import org.junit.jupiter.api.BeforeEach
import spock.lang.Issue
-import spock.lang.Shared
+import spock.lang.Specification
-@Integration(applicationClass = Application)
-class NamespacedBookSpec extends HttpClientSpec {
+import org.springframework.beans.factory.annotation.Autowired
- @Shared
- ObjectMapper objectMapper
+import grails.testing.mixin.integration.Integration
+import grails.web.http.HttpHeaders
+import org.apache.grails.testing.httpclient.HttpClientSupport
- def setup() {
- objectMapper = new ObjectMapper()
- }
+@Integration
+class NamespacedBookSpec extends Specification implements HttpClientSupport {
- @RunOnce
- @BeforeEach
- void init() {
- super.init()
- }
+ @Autowired
+ ObjectMapper objectMapper
void 'test view rendering with a namespace'() {
when: 'A request is sent to a controller with a namespace'
- HttpRequest request = HttpRequest.GET('/api/book')
- HttpResponse<Map> rsp = client.toBlocking().exchange(request, Map)
+ def rsp = httpClient.exchange('/api/book', Map)
then: 'The rsponse is correct'
- rsp.status() == HttpStatus.OK
- rsp.headers.getFirst(HttpHeaders.CONTENT_TYPE).isPresent()
- rsp.headers.getFirst(HttpHeaders.CONTENT_TYPE).get() == 'application/json;charset=UTF-8'
- rsp.body().api == 'version 1.0 (Namespaced)'
- rsp.body().title == 'API - The Shining'
+ rsp.status() == HttpStatus.OK
+ rsp.headers.getFirst(HttpHeaders.CONTENT_TYPE).isPresent()
+ rsp.headers.getFirst(HttpHeaders.CONTENT_TYPE).get() == 'application/json;charset=UTF-8'
+ rsp.body().api == 'version 1.0 (Namespaced)'
+ rsp.body().title == 'API - The Shining'
}
void 'test nested template rendering with a namespace'() {
when: 'A request is sent to a controller with a namespace'
- HttpRequest request = HttpRequest.GET('/api/book/nested')
- HttpResponse<Map> rsp = client.toBlocking().exchange(request, Map)
+ def rsp = httpClient.exchange('/api/book/nested', Map)
- then: 'The rsponse contains the child template'
+ then: 'The response contains the child template'
rsp.status() == HttpStatus.OK
rsp.headers.getFirst(HttpHeaders.CONTENT_TYPE).isPresent()
rsp.headers.getFirst(HttpHeaders.CONTENT_TYPE).get() == 'application/json;charset=UTF-8'
@@ -76,8 +63,7 @@ class NamespacedBookSpec extends HttpClientSpec {
void 'test the correct content type is chosen (json)'() {
when: 'A request is sent to a controller with a namespace'
- HttpRequest request = HttpRequest.GET('/api/book')
- HttpResponse<Map> rsp = client.toBlocking().exchange(request, Map)
+ def rsp = httpClient.exchange('/api/book', Map)
then: 'The response contains the child template'
rsp.status() == HttpStatus.OK
@@ -90,9 +76,11 @@ class NamespacedBookSpec extends HttpClientSpec {
void 'test the correct content type is chosen (hal)'() {
when: 'A request is sent to a controller with a namespace'
- HttpRequest request = HttpRequest.GET('/api/book').accept(MediaType.APPLICATION_HAL_JSON_TYPE)
- HttpResponse<String> rsp = client.toBlocking().exchange(request, String)
- Map body = objectMapper.readValue(rsp.body(), Map)
+ def rsp = httpClient.exchange(
+ HttpRequest.GET('/api/book').accept(MediaType.APPLICATION_HAL_JSON_TYPE),
+ String
+ )
+ def body = objectMapper.readValue(rsp.body(), Map)
then: 'The response contains the child template'
rsp.status() == HttpStatus.OK
@@ -105,8 +93,7 @@ class NamespacedBookSpec extends HttpClientSpec {
void 'test render(view: "..", model: ..) in controllers with namespaces works'() {
when: 'A request is sent to a controller with a namespace'
- HttpRequest request = HttpRequest.GET('/api/book/testRender')
- HttpResponse<Map> rsp = client.toBlocking().exchange(request, Map)
+ def rsp = httpClient.exchange('/api/book/testRender', Map)
then: 'The rsponse is correct'
rsp.status() == HttpStatus.OK
@@ -118,8 +105,7 @@ class NamespacedBookSpec extends HttpClientSpec {
void 'test rspond(foo, view: ..) in controllers with namespaces works'() {
when: 'A request is sent to a controller with a namespace'
- HttpRequest request = HttpRequest.GET('/api/book/testRespond')
- HttpResponse<Map> rsp = client.toBlocking().exchange(request, Map)
+ def rsp = httpClient.exchange('/api/book/testRespond', Map)
then: 'The response is correct'
rsp.status() == HttpStatus.OK
@@ -131,8 +117,7 @@ class NamespacedBookSpec extends HttpClientSpec {
void 'test respond(foo, view: ..) in controllers with namespaces works, view outside of namespace'() {
when: 'A request is sent to a controller with a namespace'
- HttpRequest request = HttpRequest.GET('/api/book/testRespondOutsideNamespace')
- HttpResponse<Map> rsp = client.toBlocking().exchange(request, Map)
+ def rsp = httpClient.exchange('/api/book/testRespondOutsideNamespace', Map)
then: 'The response is correct'
rsp.status() == HttpStatus.OK
@@ -145,8 +130,7 @@ class NamespacedBookSpec extends HttpClientSpec {
@Issue('https://github.com/apache/grails-views/issues/186')
void 'test view rendering with a namespace from a map'() {
when: 'A request is sent to a controller with a namespace'
- HttpRequest request = HttpRequest.GET('/api/book/message')
- HttpResponse<Map> rsp = client.toBlocking().exchange(request, Map)
+ def rsp = httpClient.exchange('/api/book/message', Map)
then: 'The response is correct'
rsp.status() == HttpStatus.OK
diff --git a/grails-test-examples/issue-11767/build.gradle b/grails-testing-support-httpclient/build.gradle
similarity index 54%
copy from grails-test-examples/issue-11767/build.gradle
copy to grails-testing-support-httpclient/build.gradle
index 28542fc268..486336980c 100644
--- a/grails-test-examples/issue-11767/build.gradle
+++ b/grails-testing-support-httpclient/build.gradle
@@ -16,33 +16,47 @@
* specific language governing permissions and limitations
* under the License.
*/
+
plugins {
+ id 'groovy'
+ id 'java-library'
id 'org.apache.grails.buildsrc.properties'
id 'org.apache.grails.buildsrc.compile'
+ id 'org.apache.grails.buildsrc.publish'
+ id 'org.apache.grails.buildsrc.sbom'
+ id 'org.apache.grails.gradle.grails-code-style'
}
-version = '0.1'
-group = 'issue11767.app'
+version = projectVersion
+group = 'org.apache.grails'
-apply plugin: 'org.apache.grails.gradle.grails-web'
+ext {
+ pomTitle = 'Grails Http Client Testing Support'
+ pomDescription = 'Removes boilerplate setting up a client'
+}
dependencies {
+
implementation platform(project(':grails-bom'))
- implementation 'org.apache.grails:grails-dependencies-starter-web'
- implementation 'org.apache.grails:grails-micronaut'
- implementation project(':grails-test-examples-plugins-issue-11767')
+ api 'io.micronaut:micronaut-http-client'
+
+ implementation 'org.springframework:spring-beans', {
+ // @Value(runtime)
+ }
+
+ compileOnly 'org.apache.groovy:groovy'
+ compileOnly 'org.junit.jupiter:junit-jupiter-api', {
+ // @AfterAll(runtime)
+ }
- testImplementation platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion")
- testImplementation "io.micronaut:micronaut-http-client"
- testImplementation "io.micronaut.serde:micronaut-serde-jackson"
- testImplementation 'io.micronaut:micronaut-inject-groovy'
+ runtimeOnly 'io.micronaut:micronaut-jackson-databind'
- testImplementation 'org.apache.grails:grails-testing-support-web'
+ testRuntimeOnly 'org.slf4j:slf4j-nop'
testImplementation 'org.spockframework:spock-core'
}
apply {
- from rootProject.layout.projectDirectory.file('gradle/functional-test-config.gradle')
- from rootProject.layout.projectDirectory.file('gradle/grails-extension-gradle-config.gradle')
+ from rootProject.layout.projectDirectory.file('gradle/test-config.gradle')
+ from rootProject.layout.projectDirectory.file('gradle/docs-config.gradle')
}
diff --git a/grails-testing-support-httpclient/src/main/groovy/org/apache/grails/testing/httpclient/HttpClientConfigurationUtils.groovy b/grails-testing-support-httpclient/src/main/groovy/org/apache/grails/testing/httpclient/HttpClientConfigurationUtils.groovy
new file mode 100644
index 0000000000..53275abe2c
--- /dev/null
+++ b/grails-testing-support-httpclient/src/main/groovy/org/apache/grails/testing/httpclient/HttpClientConfigurationUtils.groovy
@@ -0,0 +1,120 @@
+package org.apache.grails.testing.httpclient
+
+import java.time.Duration
+
+import groovy.transform.CompileStatic
+import groovy.transform.stc.ClosureParams
+import groovy.transform.stc.SimpleType
+
+import io.micronaut.http.client.DefaultHttpClientConfiguration
+import io.micronaut.http.client.HttpClientConfiguration
+
+@CompileStatic
+class HttpClientConfigurationUtils {
+
+ /**
+ * Builds a HttpClientConfiguration using known Micronaut HTTP client
+ * system properties (micronaut.http.client.*).
+ * Only applies properties that are set.
+ *
+ * Supported:
+ * - micronaut.http.client.read-timeout
+ * - micronaut.http.client.connect-timeout
+ * - micronaut.http.client.read-idle-timeout
+ * - micronaut.http.client.follow-redirects
+ * - micronaut.http.client.max-content-length
+ */
+ static HttpClientConfiguration fromSystemProperties() {
+ def cfg = new DefaultHttpClientConfiguration()
+
+ // durations
+ setIfPresentDuration('micronaut.http.client.read-timeout') { cfg.readTimeout = it }
+ setIfPresentDuration('micronaut.http.client.connect-timeout') { cfg.connectTimeout = it }
+ setIfPresentDuration('micronaut.http.client.read-idle-timeout') { cfg.readIdleTimeout = it }
+
+ // booleans
+ setIfPresentBoolean('micronaut.http.client.follow-redirects') { cfg.followRedirects = it }
+
+ // numbers
+ setIfPresentInteger('micronaut.http.client.max-content-length') { cfg.maxContentLength = it }
+
+ return cfg
+ }
+
+ private static void setIfPresentDuration(
+ String key,
+ @ClosureParams(value = SimpleType, options = 'java.time.Duration') Closure<?> setter
+ ) {
+ def raw = System.getProperty(key)
+ if (!raw) return
+ setter.call(parseDuration(raw, key))
+ }
+
+ private static void setIfPresentBoolean(
+ String key,
+ @ClosureParams(value = SimpleType, options = 'boolean') Closure<?> setter
+ ) {
+ def raw = System.getProperty(key)
+ if (!raw) return
+ setter.call(Boolean.parseBoolean(raw.trim()))
+ }
+
+ private static void setIfPresentInteger(
+ String key,
+ @ClosureParams(value = SimpleType, options = 'int') Closure<?> setter
+ ) {
+ def raw = System.getProperty(key)
+ if (!raw) return
+ try {
+ setter.call(Integer.parseInt(raw.trim()))
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("System property '$key' must be an integer, was '$raw'", e)
+ }
+ }
+
+ /**
+ * Accepts:
+ * - Micronaut style: 20s, 200ms, 1m, 2h, 1d
+ * - ISO-8601: PT20S, PT200MS, etc.
+ * - plain number (treated as milliseconds): 20000
+ */
+ private static Duration parseDuration(String raw, String key) {
+ def v = raw.trim()
+ if (!v) throw new IllegalArgumentException("System property '$key' was blank")
+
+ // ISO-8601
+ if (v.startsWith('P')) {
+ try {
+ return Duration.parse(v)
+ } catch (Exception e) {
+ throw new IllegalArgumentException("System property '$key' must be an ISO-8601 duration, was '$raw'", e)
+ }
+ }
+
+ // plain number -> millis
+ if (v ==~ /^\d+$/) {
+ return Duration.ofMillis(Long.parseLong(v))
+ }
+
+ // suffix format
+ def m = (v =~ /^(\d+)\s*(ms|s|m|h|d)$/)
+ if (!m.matches()) {
+ throw new IllegalArgumentException(
+ "System property '$key' must be like '20s', '200ms', '1m', '2h', '1d' or ISO-8601 'PT20S'. Was '$raw'"
+ )
+ }
+
+ long n = Long.parseLong(m.group(1))
+ String unit = m.group(2)
+
+ switch (unit) {
+ case 'ms': return Duration.ofMillis(n)
+ case 's' : return Duration.ofSeconds(n)
+ case 'm' : return Duration.ofMinutes(n)
+ case 'h' : return Duration.ofHours(n)
+ case 'd' : return Duration.ofDays(n)
+ default:
+ throw new IllegalStateException("Unhandled duration unit '$unit' for '$key'")
+ }
+ }
+}
diff --git a/grails-testing-support-httpclient/src/main/groovy/org/apache/grails/testing/httpclient/HttpClientSupport.groovy b/grails-testing-support-httpclient/src/main/groovy/org/apache/grails/testing/httpclient/HttpClientSupport.groovy
new file mode 100644
index 0000000000..3afefd5406
--- /dev/null
+++ b/grails-testing-support-httpclient/src/main/groovy/org/apache/grails/testing/httpclient/HttpClientSupport.groovy
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.grails.testing.httpclient
+
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+
+import io.micronaut.http.client.BlockingHttpClient
+import io.micronaut.http.client.HttpClient
+import io.micronaut.http.client.HttpClientConfiguration
+import org.junit.jupiter.api.AfterAll
+
+import org.springframework.beans.factory.annotation.Value
+
+/**
+ * Test support trait that provides a lazily-created Micronaut {@link HttpClient} for HTTP-based tests.
+ * <p>
+ * The client is stored in a {@link ThreadLocal} so that tests can reuse it safely (for example, when
+ * running in parallel across threads).
+ * <p>
+ * Typical usage in a Grails Spock integration specification (where {@code serverPort} is provided):
+ * <pre>
+ * import grails.testing.mixin.integration.Integration
+ * import spock.lang.Specification
+ *
+ * @Integration
+ * class MySpec extends Specification implements HttpClientSupport {
+ *
+ * void 'health endpoint responds OK'() {
+ * when:
+ * def rsp = httpClient.toBlocking().exchange('/health')
+ *
+ * then:
+ * rsp.status.code == 200
+ * }
+ * }
+ * </pre>
+ * <p>
+ * If your test doesn't provide a {@code serverPort} property, override {@code getHttpClientURL()} to
+ * point the client at the desired server.
+ */
+@CompileStatic
+trait HttpClientSupport {
+
+ @Value('${local.server.port}')
+ int assignedPort = -1
+
+ /**
+ * Holds the shared client for the current thread so it can be reused and
+ * automatically closed after all tests have run.
+ */
+ private static final ThreadLocal<HttpClient> sharedClientHolder = new ThreadLocal<>()
+
+ /**
+ * Holds any clients created via {@code newHttpClient(HttpClientConfiguration, String)}
+ * for the current thread so they can be automatically closed after the test.
+ */
+ private static final ThreadLocal<List<HttpClient>> customClientsHolder = new ThreadLocal<>()
+
+ private HttpClient initClient(HttpClientConfiguration config = null, String url = null) {
+ config ?
+ HttpClient.create(new URL(url), config) :
+ HttpClient.create(new URL(url))
+ }
+
+ /**
+ * @return the shared (per-thread) client instance,
+ * as a blocking client, creating it on first access.
+ */
+ BlockingHttpClient getHttpClient() {
+ asyncClient.toBlocking()
+ }
+
+ /**
+ * @return the shared (per-thread) client instance
+ */
+ HttpClient getAsyncClient() {
+ def client = sharedClientHolder.get()
+ if (!client) {
+ client = initClient(httpClientConfig, httpClientURL)
+ sharedClientHolder.set(client)
+ }
+ client
+ }
+
+ /**
+ * Creates a new client instance.
+ * <p>
+ * The returned client instance is not stored as the shared {@code httpClient}, but it is tracked
+ * for the current thread and will be closed automatically by after the spec.
+ * <p>
+ * This is useful for tests that need an isolated client configuration.
+ * <p>
+ * If {@code config} is {@code null}, {@code getHttpClientConfig()} will be used.
+ * If {@code url} is {@code null}, {@code getHttpClientURL()} will be used.
+ */
+ HttpClient newHttpClient(HttpClientConfiguration config = null, String url = null) {
+ def client = initClient(
+ config ?: null,
+ url ?: httpClientURL
+ )
+ storeCustomClient(client)
+ client
+ }
+
+ private void storeCustomClient(HttpClient client) {
+ if (customClientsHolder.get() == null) {
+ customClientsHolder.set([])
+ }
+ customClientsHolder.get().add(client)
+ }
+
+ /**
+ * Override to provide custom Micronaut client configuration.
+ *
+ * @return the configuration to use when creating the shared client
+ */
+ HttpClientConfiguration getHttpClientConfig() {
+ HttpClientConfigurationUtils.fromSystemProperties()
+ }
+
+ /**
+ * Base URL used when creating the client.
+ * <p>
+ * By default this targets localhost and uses the {@code serverPort} property from the test.
+ */
+ String getHttpClientURL() {
+ if (assignedPort == -1) {
+ throw new IllegalStateException(
+ 'No server port assigned. ' +
+ 'Did you remember to annotate your test ' +
+ 'class with @Integration or @SpringBootTest?'
+ )
+ }
+ "http://localhost:$assignedPort"
+ }
+
+ /**
+ * Closes any clients created by this support trait and clears internal {@link ThreadLocal}s.
+ */
+ @AfterAll
+ @CompileDynamic
+ static void cleanupClients() {
+ sharedClientHolder.get()?.close()
+ def customClients = customClientsHolder.get()
+ customClients?.each {
+ try {
+ it.close()
+ } catch (ignored) {
+
+ }
+ }
+ sharedClientHolder.remove()
+ customClientsHolder.remove()
+ }
+}
diff --git a/grails-testing-support-httpclient/src/test/groovy/org/apache/grails/testing/httpclient/HttpClientConfigurationUtilsSpec.groovy b/grails-testing-support-httpclient/src/test/groovy/org/apache/grails/testing/httpclient/HttpClientConfigurationUtilsSpec.groovy
new file mode 100644
index 0000000000..52540f0ec3
--- /dev/null
+++ b/grails-testing-support-httpclient/src/test/groovy/org/apache/grails/testing/httpclient/HttpClientConfigurationUtilsSpec.groovy
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.grails.testing.httpclient
+
+import java.time.Duration
+
+import spock.lang.Specification
+import spock.lang.Unroll
+import spock.util.environment.RestoreSystemProperties
+
+@RestoreSystemProperties
+class HttpClientConfigurationUtilsSpec extends Specification {
+
+ @Unroll
+ void 'httpClientConfigurationFromSystemProperties parses duration format: #raw -> #expected'() {
+ given:
+ System.setProperty('micronaut.http.client.read-timeout', raw)
+
+ when:
+ def cfg = HttpClientConfigurationUtils.fromSystemProperties()
+
+ then:
+ cfg.readTimeout.orElse(null) == expected
+
+ where:
+ raw || expected
+ '20s' || Duration.ofSeconds(20)
+ '200ms' || Duration.ofMillis(200)
+ '1m' || Duration.ofMinutes(1)
+ '2h' || Duration.ofHours(2)
+ '1d' || Duration.ofDays(1)
+ '20000' || Duration.ofMillis(20000)
+ 'PT20S' || Duration.ofSeconds(20)
+ 'PT0.2S' || Duration.ofMillis(200)
+ ' 20s ' || Duration.ofSeconds(20)
+ }
+
+ void 'httpClientConfigurationFromSystemProperties rejects invalid duration formats with helpful message'() {
+ given:
+ System.setProperty('micronaut.http.client.read-timeout', 'nope')
+
+ when:
+ HttpClientConfigurationUtils.fromSystemProperties()
+
+ then:
+ def e = thrown(IllegalArgumentException)
+ e.message.contains("micronaut.http.client.read-timeout")
+ }
+
+ void 'httpClientConfigurationFromSystemProperties rejects blank duration'() {
+ given:
+ System.setProperty('micronaut.http.client.read-timeout', ' ')
+
+ when:
+ HttpClientConfigurationUtils.fromSystemProperties()
+
+ then:
+ def e = thrown(IllegalArgumentException)
+ e.message.contains("micronaut.http.client.read-timeout")
+ }
+}
diff --git a/grails-testing-support-httpclient/src/test/groovy/org/apache/grails/testing/httpclient/HttpClientSupportSpec.groovy b/grails-testing-support-httpclient/src/test/groovy/org/apache/grails/testing/httpclient/HttpClientSupportSpec.groovy
new file mode 100644
index 0000000000..c211029d15
--- /dev/null
+++ b/grails-testing-support-httpclient/src/test/groovy/org/apache/grails/testing/httpclient/HttpClientSupportSpec.groovy
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.grails.testing.httpclient
+
+import io.micronaut.http.client.BlockingHttpClient
+import spock.lang.Specification
+
+class HttpClientSupportSpec extends Specification {
+
+ void 'getHttpClientURL throws if no port has been assigned'() {
+ given:
+ def support = new TestSupport(assignedPort: -1)
+
+ when:
+ support.httpClient
+
+ then:
+ def e = thrown(IllegalStateException)
+ e.message.contains('No server port assigned')
+ }
+
+ void 'shared async client is reused and then recreated after cleanupClient'() {
+ given:
+ def support = new TestSupport(assignedPort: 0)
+
+ when: 'first access creates the shared async client'
+ def c1 = support.asyncClient
+ def c2 = support.asyncClient
+
+ then: 'the shared client is reused'
+ c1.is(c2)
+
+ when: 'cleanup runs'
+ support.cleanupClients()
+
+ and: 'access again'
+ def c3 = support.asyncClient
+
+ then: 'a new shared client is created'
+ !c3.is(c1)
+
+ cleanup:
+ support.cleanupClients()
+ }
+
+ void 'cleanupClient closes custom clients created via newHttpClient'() {
+ given:
+ def support = new TestSupport(assignedPort: 0)
+
+ and: 'we create custom clients and observe close()'
+ def closeCount = 0
+ def c1 = support.newHttpClient()
+ def c2 = support.newHttpClient()
+
+ [c1, c2].each {
+ it.metaClass.close = { -> closeCount++ }
+ }
+
+ when:
+ support.cleanupClients()
+
+ then:
+ closeCount == 2
+ }
+
+ void 'httpClient returns a BlockingHttpClient wrapper'() {
+ given:
+ def support = new TestSupport(assignedPort: 0)
+
+ expect:
+ support.httpClient instanceof BlockingHttpClient
+ }
+
+ private static class TestSupport implements HttpClientSupport {}
+}
diff --git a/settings.gradle b/settings.gradle
index ee920e9ec2..6ed4d2e2d9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -91,10 +91,6 @@ include(
'grails-shell-cli',
'grails-spring',
'grails-test-core',
- 'grails-testing-support-core',
- 'grails-testing-support-mongodb',
- 'grails-testing-support-views-gson',
- 'grails-testing-support-web',
'grails-url-mappings',
'grails-validation',
'grails-views-core',
@@ -170,10 +166,15 @@ include(
//'grails-datamapping-rx',
// Testing Support
+ 'grails-testing-support-core',
'grails-testing-support-datamapping',
'grails-testing-support-dbcleanup-core',
'grails-testing-support-dbcleanup-h2',
'grails-testing-support-dbcleanup-postgresql',
+ 'grails-testing-support-httpclient',
+ 'grails-testing-support-mongodb',
+ 'grails-testing-support-views-gson',
+ 'grails-testing-support-web',
// Test Report
'grails-data-test-report',