////
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.
////

Integration tests differ from unit tests in that you have full access to the Grails environment within the test. You can create an integration test using the link:{commandLineRef}create-integration-test.html[create-integration-test] command:

[source,groovy]
----
$ grails create-integration-test Example
----

The above command will create a new integration test at the location `src/integration-test/groovy/<PACKAGE>/ExampleSpec.groovy`.

Grails uses the test environment for integration tests and loads the application prior to the first test run. All tests use the same application state.


==== Transactions


Integration test methods run inside their own database transaction by default, which is rolled back at the end of each test method. This means that data saved during a test is not persisted to the database (which is shared across all tests). The default generated integration test template includes the link:{api}grails/gorm/transactions/Rollback.html[Rollback] annotation:

[source,groovy]
----
import grails.testing.mixin.integration.Integration
import grails.gorm.transactions.*
import spock.lang.*

@Integration
@Rollback
class ExampleSpec extends Specification {

    ...

    void "test something"() {
        expect:"fix me"
            true == false
    }
}
----

The `Rollback` annotation ensures that each test method runs in a transaction that is rolled back. Generally this is desirable because you do not want your tests depending on order or application state.

In Grails 3.0 tests rely on `grails.gorm.transactions.Rollback` annotation to bind the session in integration tests. Though each test method transaction is rolled back, the `setup()` method uses a separate transaction that is not rolled back.
Data will persist to the database and will need to be cleaned up manually if `setup()` sets up data and persists them as shown in the below sample:

[source,groovy]
----
import grails.testing.mixin.integration.Integration
import grails.gorm.transactions.*
import spock.lang.*

@Integration
@Rollback
class BookSpec extends Specification {

    void setup() {
        // Below line would persist and not roll back
        new Book(name: 'Grails in Action').save(flush: true)
    }

    void "test something"() {
        expect:
        Book.count() == 1
    }
}
----

To preload the database and automatically roll back setup logic, any persistence operations need to be called from the test method itself so that they can run within the test method's rolled back transaction. Similar to usage of the `setupData()` method shown below which creates a record in database and after running other test will be rolled back:

[source,groovy]
----
import grails.testing.mixin.integration.Integration
import grails.gorm.transactions.*
import spock.lang.*

@Integration
@Rollback
class BookSpec extends Specification {

    void setupData() {
        // Below line would roll back
        new Book(name: 'Grails in Action').save(flush: true)
    }

    void "test something"() {
        given:
        setupData()

        expect:
        Book.count() == 1
    }
}
----


==== Using Spring's Rollback annotation

Another transactional approach could be to use Spring's {springapi}org/springframework/test/annotation/Rollback.html[@Rollback] instead.

[source,groovy]
----
import grails.testing.mixin.integration.Integration
import org.springframework.test.annotation.Rollback
import spock.lang.*

@Integration
@Rollback
class BookSpec extends Specification {

    void setup() {
        new Book(name: 'Grails in Action').save(flush: true)
    }

    void "test something"() {
        expect:
        Book.count() == 1
    }
}
----

NOTE: It isn't possible to make `grails.gorm.transactions.Rollback` behave the same way as Spring's Rollback annotation because `grails.gorm.transactions.Rollback` transforms the byte code of the class, eliminating the need for a proxy (which Spring's version requires).
This has the downside that you cannot implement it differently for different cases (as Spring does for testing).


==== DirtiesContext


If you do have a series of tests that will share state you can remove the `Rollback` and the last test in the suite should feature the {springapi}org/springframework/test/annotation/DirtiesContext.html[DirtiesContext] annotation which will shutdown the environment and restart it fresh (note that this will have an impact on test run times).


==== DatabaseCleanup


As an alternative to `@Rollback`, the `@DatabaseCleanup` annotation truncates all database tables after each test method, rather than rolling back the transaction. This is useful when your tests need data to actually be committed to the database (for example, to test behavior that depends on committed data or to verify transactional boundaries).

===== Supported Databases

Database cleanup is automatically discovered and applied based on your datasource configuration. The following database implementations are available:

====== H2 Database

To use `@DatabaseCleanup` with H2 databases, add the `grails-testing-support-dbcleanup-h2` dependency:

[source,groovy]
----
dependencies {
    integrationTestImplementation 'org.apache.grails:grails-testing-support-dbcleanup-h2'
}
----

This module will clean up schema the specified schema by default, otherwise it will purge from all schemas.

====== PostgreSQL

To use `@DatabaseCleanup` with PostgreSQL databases, add the `grails-testing-support-dbcleanup-postgresql` dependency:

[source,groovy]
----
dependencies {
    integrationTestImplementation 'org.apache.grails:grails-testing-support-dbcleanup-postgresql'
}
----

This module will clean up the schema specified by `currentSchema` in the jdbc url, or it will clean up all non-system schemas.

===== Using DatabaseCleanup

To use `@DatabaseCleanup`, add the appropriate cleanup module for your database to your integration test configuration. The annotation can be applied at the class level to clean up after every test:

[source,groovy]
----
import grails.gorm.transactions.Transactional
import grails.testing.mixin.integration.Integration
import org.apache.grails.testing.cleanup.core.DatabaseCleanup
import spock.lang.Specification
import spock.lang.Stepwise

@Integration
@DatabaseCleanup
@Stepwise
class BookCleanupSpec extends Specification {

    @Transactional
    void "test 1 - create data"() {
        when:
        new Book(title: 'Grails in Action').save(flush: true, failOnError: true)

        then:
        Book.count() == 1
    }

    @Transactional
    void "test 2 - data was cleaned up"() {
        expect: 'no data from the previous test'
        Book.count() == 0
    }
}
----

It can also be applied at the method level, so only annotated test methods trigger cleanup:

[source,groovy]
----
@Integration
class SelectiveCleanupSpec extends Specification {

    @DatabaseCleanup
    @Transactional
    void "this test triggers cleanup"() {
        // data will be truncated after this test
    }

    @Transactional
    void "this test does not trigger cleanup"() {
        // data persists for the next test
    }
}
----

You can optionally specify which datasource beans to clean by passing their names:

[source,groovy]
----
@DatabaseCleanup(['dataSource', 'dataSource_secondary'])
----

You can also explicitly map each datasource to a database type, ensuring the correct `DatabaseCleaner` implementation is used without relying on auto-discovery:

[source,groovy]
----
@DatabaseCleanup(['dataSource:h2', 'dataSource_pg:postgresql'])
----

These formats can be mixed. Entries without a `:` separator will auto-discover the appropriate cleaner:

[source,groovy]
----
@DatabaseCleanup(['dataSource:h2', 'dataSource_other'])
----

If no names are specified, all datasources in the application context are cleaned.

===== Deferred Cleanup (cleanupAfterSpec)

By default, `@DatabaseCleanup` truncates tables after each individual test method. If you prefer to defer cleanup until after the entire spec finishes, set `cleanupAfterSpec = true`:

[source,groovy]
----
@Integration
@DatabaseCleanup(cleanupAfterSpec = true)
@Stepwise
class SharedDataSpec extends Specification {

    @Transactional
    void "test 1 - create data"() {
        when:
        new Book(title: 'Grails in Action').save(flush: true, failOnError: true)

        then:
        Book.count() == 1
    }

    @Transactional
    void "test 2 - data from test 1 is still available"() {
        expect: 'data persists across tests'
        Book.count() == 1
    }

    // Database is cleaned once after both tests complete
}
----

This is useful when test methods build on each other's data, or when per-test cleanup is too expensive and a single cleanup at the end of the spec is sufficient.

NOTE: `cleanupAfterSpec` is only valid on class-level `@DatabaseCleanup` annotations. Using it on a method-level annotation will throw an `IllegalStateException`.

NOTE: If individual methods within a `cleanupAfterSpec = true` spec also have their own `@DatabaseCleanup` annotation, those methods will still be cleaned up immediately after execution.

WARNING: `@DatabaseCleanup` truncates *all* tables, including any data inserted by `BootStrap`. Tests using this annotation should create their own test data and not rely on bootstrap seed data.

IMPORTANT: `@DatabaseCleanup` requires `@Integration`. Using `@DatabaseCleanup` on a spec without `@Integration` will throw an `IllegalStateException`, since the database cleanup framework depends on the Spring application context provided by the integration test infrastructure.

NOTE: `@DatabaseCleanup` should not be placed on non-feature methods such as `setup()`, `cleanup()`, `setupSpec()`, or `cleanupSpec()`. To catch misplaced annotations, enable the validation property described below.

===== System Properties

The `@DatabaseCleanup` process supports two system properties that can be passed via `-D` flags:

[cols="2,3,1"]
|===
| Property | Description | Default

| `grails.testing.cleanup.debug`
| When set to `true`, prints a formatted report of cleanup statistics to stdout after each cleanup operation, showing datasource name, table names, and row counts before truncation.
| `false`

| `grails.testing.cleanup.validate`
| When set to `true`, validates that `@DatabaseCleanup` is not placed on non-feature methods (`setup()`, `cleanup()`, `setupSpec()`, `cleanupSpec()`). This validation uses reflection to iterate over all declared methods and is disabled by default for performance.
| `false`
|===

Example usage:

[source,bash]
----
./gradlew integrationTest -Dgrails.testing.cleanup.debug=true -Dgrails.testing.cleanup.validate=true
----


[[httpClientTestingSupport]]
==== HTTP Client Testing Support

For integration and functional tests that need to interact with live HTTP endpoints, Grails provides the
`grails-testing-support-http-client` module.

Add the dependency to your integration test configuration:

[source,groovy]
----
dependencies {
    integrationTestImplementation 'org.apache.grails:grails-testing-support-http-client'
}
----

You can then implement the `HttpClientSupport` trait in your Spock specification and use helper methods for
request building and response assertions:

[source,groovy]
----
import java.net.http.HttpClient
import java.net.http.HttpTimeoutException
import java.time.Duration

import spock.lang.Specification

import grails.testing.mixin.integration.Integration
import org.apache.grails.testing.http.client.HttpClientSupport
import org.apache.grails.testing.http.client.MultipartBody

@Integration
class DemoSpec extends Specification implements HttpClientSupport {

    void 'simple GET with status and body assertions'() {
        when: 'invoking a GET request to the /health endpoint'
        def response = http('/health')

        then: 'verifies status, headers and body contents via fluent API'
        response.assertStatus(200)
                .assertHeaders('Content-Type': 'application/json')
                .assertContains('UP')


        and: 'can also verify all these under the hood in one go'
        response.assertContains(200, 'Content-Type': 'application/json', 'UP')
    }

    void 'GET with headers and header assertions'() {
        when:
        def response = http('/api/info', 'Accept': 'application/json')

        then:
        response.assertHeadersIgnoreCase(200, 'content-type': 'application/json;charset=UTF-8')
    }

    void 'Posting and verifying JSON'() {
        when: 'you post a map it is turned into a JSON payload'
        def response = httpPostJson('/api/books', [title: 'Grails in Action', pages: 500])

        then: 'you can also verify JSON response bodies with maps'
        response.assertJson(201, [id: 1, title: 'Grails in Action', pages: 500])

        and: 'you can also verify against a JSON string'
        response.assertJson('''
            {
                "id": 1,
                "title": "Grails in Action",
                "pages": 500
            }
        ''')

        and: 'the canonicalized JSON tree structure is compared, not strings'
        response.assertJson('{ "pages": 500, "id": 1, "title": "Grails in Action" }')

        and: 'if you want to compare strings, use assertContains/assertNotContains'
        response.assertNotContains('{ "id": 1, "title": "Grails in Action", "pages": 500 }')

        and: 'you can also use assertJsonContains to verify sub trees in the response json'
        response.assertJsonContains([title: 'Grails in Action'])
    }

    void 'Posting and verifying XML'() {
        when: 'you can post XML using a builder'
        def response = httpPostXml('/api/books') {
            book {
                title('Grails in Action')
                pages(500)
            }
        }

        then: 'and you can verify XML responses with assertions on GPathResult'
        with(response.xml()) {
            title.text() == 'Grails in Action'
            pages.text().toInteger() == 500
        }
    }

    void 'custom request and custom client configuration'() {
        when: 'you need a custom configured client'
        def noRedirectClient = newHttpClientWith {
            followRedirects(HttpClient.Redirect.NEVER)
        }

        and: 'or a custom configured request'
        def request = newHttpRequestWith('/api/slow') {
            timeout(Duration.ofSeconds(1))
        }

        when:
        sendHttpRequest(request, noRedirectClient)

        then:
        thrown(HttpTimeoutException)
    }

    void 'multipart upload'() {
        given:
        def body = MultipartBody.builder()
                .addPart('description', 'test file')
                .addPart('file', 'hello.txt', 'text/plain', 'hello world')
                .build()

        when:
        def response = httpPostMultipart('/api/upload', body)

        then:
        response.assertStatus(200)
    }
}
----

`HttpClientSupport` uses the JDK `HttpClient` under the hood and applies default request and connect timeouts of
60 seconds and a redirect policy of always following redirects. You can use helpers `newHttpRequestWith(Closure configurer)`
and `newHttpClientWith(Closure configurer)` to customize your own client and requests starting from the defaults.

Additional request helper methods available include `httpDelete()`, `httpHead()`, `httpOptions()`, `httpPatch()`,
`httpPut()`, `httpTrace()`, and many assertion methods on responses.

===== Custom JSON Parsing

When response JSON needs non-default `JsonSlurper` behavior, configure it fluently on the response wrapper:

[source,groovy]
----
import groovy.json.JsonParserType

given:
def response = http('/config')

expect:
response
    .withJsonSlurper(parserType: JsonParserType.LAX, checkDates: true)
    .assertJsonContains('{featureFlag:true}')
----

Supported named options mirror the `JsonSlurper` settings exposed by `JsonUtils.SlurperConfig`, including
`parserType`, `checkDates`, `chop`, `lazyChop`, and `maxSizeForInMemory`.

===== Custom XML Parsing

Response XML parsing uses a secure default `XmlSlurper` configuration. It is namespace-aware, non-validating,
allows inline `DOCTYPE` declarations, and disables external entity expansion plus external DTD loading.

When a test needs different XML parsing behavior, override it fluently on the response wrapper:

[source,groovy]
----
import groovy.xml.XmlSlurper

given:
def response = http('/feed')

when:
def xml = response.withXmlSlurper(factory: { new XmlSlurper(false, false) }).xml()

then:
xml.channel.item.size() == 10
----

`withXmlSlurper(...)` accepts an `XmlUtils.SlurperConfig`. The default configuration is secure; the
`factory` hook is available for tests that need a fully custom parser instance.

===== JSON payload formatting

JSON request bodies can be sent in three ways depending on how much formatting control you need.

For exact control over the payload text, pass a pre-rendered JSON string to the request helper:

[source,groovy]
----
httpPostJson('/products', '{"name":"Widget","tags":["new","sale"]}')
----

For the common case, use the `http[Post|Put|Patch]Json` helpers with a `Map`. They serialize the payload with
Groovy's default `JsonOutput.toJson(...)` behavior:

[source,groovy]
----
httpPostJson('/products', [name: 'Widget', qty: 2])
----

When you want custom JSON formatting, pass a `JsonGenerator` alongside the `Map` payload. This lets you control
rendering details such as null handling, field exclusions, converters, and date formatting:

[source,groovy]
----
import groovy.json.JsonGenerator

def jsonGenerator = new JsonGenerator.Options()
    .excludeNulls()
    .dateFormat('yyyy-MM-dd')
    .build()

httpPutJson('/products/1', jsonGenerator, [
    name: 'Widget',
    discontinuedAt: null,
    releaseDate: new Date()
])
----

The same `JsonGenerator` overload pattern is available on `httpPostJson(...)`, `httpPutJson(...)`, and
`httpPatchJson(...)`, including the variants that accept request headers and an explicit `HttpClient`.

===== XML formatting

XML request bodies can be generated with Groovy MarkupBuilder DSL. For the `http[Post|Patch|Put]Xml` methods,
`HttpClientSupport` accepts an optional `XmlUtils.Format` instance, which constructor takes named params to
configure the format:

[source,groovy]
----
import org.apache.grails.testing.http.client.utils.XmlUtils

when:
def response = httpPostXml('/products', new XmlUtils.Format(
    omitDeclaration: false,
    prettyPrint: true,
    indent: '  ',
    lineSeparator: '\n',
    doctype: '<!DOCTYPE product SYSTEM "product.dtd">',
    omitNullAttributes: true,
    omitEmptyAttributes: true,
    spaceInEmptyElements: false,
    escapeAttributes: true
)) {
    product(id: '1', description: null, sku: '') {
        name('Widget')
        empty()
    }
}

then:
response.assertStatus(201)

----

When you need the same formatting control for other request helpers, render the XML first and then pass the
result to `httpPost`, `httpPut`, or `httpPatch` with `application/xml`:

[source,groovy]
----
import org.apache.grails.testing.http.client.utils.XmlUtils

def payload = XmlUtils.toXml(
    omitDeclaration: false,
    prettyPrint: true,
    indent: '    ',
    lineSeparator: '\n',
    doctype: '<!DOCTYPE product SYSTEM "product.dtd">',
    omitNullAttributes: true,
    omitEmptyAttributes: true,
    spaceInEmptyElements: false,
    escapeAttributes: true
) {
    product(id: '1', description: null, sku: '') {
        name('Widget')
        empty()
    }
}

httpPut('/products/1', payload, 'application/xml')
----

Supported `XmlUtils.Format` options include:

- Declaration and document metadata: `omitDeclaration`, `xmlVersion`, `charset`, `doubleQuotes`, `doctype`
- Pretty-printing layout: `prettyPrint`, `indent`, `lineSeparator`
- Empty elements and attribute handling: `expandEmptyElements`, `spaceInEmptyElements`,
`omitEmptyAttributes`, `omitNullAttributes`
- Attribute escaping: `escapeAttributes`

`XmlUtils.toXml(...)` also supports inline named options, so shorter cases can be written as:

[source,groovy]
----

def payload = XmlUtils.toXml(omitNullAttributes: true, spaceInEmptyElements: false) {
    product(name: 'Widget', description: null) {
        empty()
    }
}

httpPost('/products', payload, 'application/xml')
----

==== Autowiring


To obtain a reference to a bean you can use the {springapi}org/springframework/beans/factory/annotation/Autowired.html[Autowired] annotation. For example:

[source,groovy]
----
...
import org.springframework.beans.factory.annotation.*

@Integration
@Rollback
class ExampleServiceSpec extends Specification {

    @Autowired
    ExampleService exampleService
    ...

    void "Test example service"() {
        expect:
            exampleService.countExamples() == 0
    }
}
----



==== Testing Controllers


To integration test controllers it is recommended you use link:{commandLineRef}create-functional-test.html[create-functional-test] command to create a Geb functional test. See the following section on functional testing for more information.
