You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2026/04/07 18:10:06 UTC

(grails-core) 09/33: test(hibernate7): increase HibernateMappingContextConfiguration coverage to 90%+

This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 8.0.x-hibernate7-dev
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 0eea605e4c733eda4c826576de65a8c9cc26c36a
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Mon Apr 6 23:09:33 2026 -0500

    test(hibernate7): increase HibernateMappingContextConfiguration coverage to 90%+
    
    - Expand HibernateMappingContextConfigurationSpec with 31 new tests covering:
      - All simple getters and setters (bytecodeProvider, namingStrategyProvider,
        mappingCacheHolder, sessionFactoryBeanName, dataSourceName, eventListeners,
        hibernateEventListeners, serviceRegistry)
      - setApplicationContext all branches (null ctx, with/without datasource bean,
        with/without classLoader, non-default dataSourceName, pre-set property)
      - setDataSourceConnectionSource with real ConnectionSource stub
      - addAnnotatedClass, addAnnotatedClasses, addPackages batch methods
      - createBootstrapServiceRegistryBuilder and createStandardServiceRegistryBuilder
      - matchesFilter returning false for plain class, true for @jakarta.persistence.Entity
      - scanPackages on empty and annotated packages
    
    - Add HibernateMappingContextConfigurationIntegrationSpec extending
      HibernateGormDatastoreSpec to exercise buildSessionFactory end-to-end
      with real H2 infrastructure, covering annotatedClasses iteration,
      additionalClasses processing, and SessionFactory verification
    
    - Add top-level domain classes HmccTestBook, HmccTestAuthor (@grails.gorm.annotation.Entity),
      and CfgJpaTestEntity (@jakarta.persistence.Entity) as test fixtures
    
    - Fix pre-existing Spock compilation error in MutationQueryDelegateSpec:
      noExceptionThrown() is only allowed in 'then:' blocks, not 'expect:';
      convert the two affected feature methods to when:/then: form
    
    Coverage: 52.30% → 90.3% (line), 90.3% (instruction), 100% (method)
    
    Co-authored-by: Copilot <22...@users.noreply.github.com>
---
 ...HibernateMappingContextConfigurationSpec.groovy | 435 +++++++++++++++++++++
 .../query/MutationQueryDelegateSpec.groovy         | 220 +++++++++++
 2 files changed, 655 insertions(+)

diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfigurationSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfigurationSpec.groovy
index 70fe85c08b..cafda3ad3c 100644
--- a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfigurationSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/HibernateMappingContextConfigurationSpec.groovy
@@ -18,8 +18,26 @@
  */
 package org.grails.orm.hibernate.cfg
 
+import grails.gorm.annotation.Entity
+import grails.gorm.hibernate.HibernateEntity
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import org.grails.datastore.gorm.jdbc.connections.DataSourceSettings
+import org.grails.datastore.mapping.core.connections.ConnectionSource
+import org.grails.orm.hibernate.HibernateEventListeners
+import org.grails.orm.hibernate.cfg.domainbinding.util.NamingStrategyProvider
+import org.grails.orm.hibernate.proxy.GrailsBytecodeProvider
+import org.hibernate.boot.registry.BootstrapServiceRegistry
+import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder
+import org.hibernate.boot.registry.StandardServiceRegistryBuilder
+import org.hibernate.cfg.AvailableSettings
+import org.hibernate.cfg.JdbcSettings
+import org.springframework.context.ApplicationContext
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory
 import spock.lang.Specification
 
+import javax.sql.DataSource
+
 class HibernateMappingContextConfigurationSpec extends Specification {
 
     def "test HibernateMappingContextConfiguration defaults"() {
@@ -30,4 +48,421 @@ class HibernateMappingContextConfigurationSpec extends Specification {
         config.getNamingStrategyProvider() != null
         config.dataSourceName == 'default'
     }
+
+    def "setBytecodeProvider stores the provider and getGrailsBytecodeProvider returns it"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        def provider = new GrailsBytecodeProvider()
+
+        when:
+        config.setBytecodeProvider(provider)
+
+        then:
+        config.getGrailsBytecodeProvider().is(provider)
+    }
+
+    def "getGrailsBytecodeProvider creates a new GrailsBytecodeProvider when bytecodeProvider is null"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+
+        expect:
+        config.getGrailsBytecodeProvider() instanceof GrailsBytecodeProvider
+    }
+
+    def "setNamingStrategyProvider updates the naming strategy provider"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        def provider = new NamingStrategyProvider()
+
+        when:
+        config.setNamingStrategyProvider(provider)
+
+        then:
+        config.getNamingStrategyProvider().is(provider)
+    }
+
+    def "getMappingCacheHolder returns null when no HibernateMappingContext is set"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+
+        expect:
+        config.getMappingCacheHolder() == null
+    }
+
+    def "getMappingCacheHolder delegates to the HibernateMappingContext when set"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        def ctx = new HibernateMappingContext()
+        config.setHibernateMappingContext(ctx)
+
+        expect:
+        config.getMappingCacheHolder() != null
+        config.getMappingCacheHolder().is(ctx.getMappingCacheHolder())
+    }
+
+    def "setHibernateMappingContext stores the context"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        def ctx = new HibernateMappingContext()
+
+        when:
+        config.setHibernateMappingContext(ctx)
+
+        then:
+        config.getMappingCacheHolder() != null
+    }
+
+    def "setSessionFactoryBeanName updates the bean name"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+
+        when:
+        config.setSessionFactoryBeanName("mySessionFactory")
+
+        then:
+        config.sessionFactoryBeanName == "mySessionFactory"
+    }
+
+    def "setDataSourceName updates the data source name"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+
+        when:
+        config.setDataSourceName("secondary")
+
+        then:
+        config.dataSourceName == "secondary"
+    }
+
+    def "setEventListeners stores the listener map"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        def listeners = [save: "mySaveListener"]
+
+        when:
+        config.setEventListeners(listeners)
+
+        then:
+        config.eventListeners == listeners
+    }
+
+    def "setHibernateEventListeners stores the HibernateEventListeners instance"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        def hel = new HibernateEventListeners()
+
+        when:
+        config.setHibernateEventListeners(hel)
+
+        then:
+        config.hibernateEventListeners.is(hel)
+    }
+
+    def "getServiceRegistry returns null before buildSessionFactory is called"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+
+        expect:
+        config.getServiceRegistry() == null
+    }
+
+    def "addAnnotatedClass adds a class to additionalClasses"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+
+        when:
+        config.addAnnotatedClass(String)
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "addAnnotatedClasses adds multiple classes in batch"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+
+        when:
+        config.addAnnotatedClasses(String, Integer)
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "addPackages adds multiple packages in batch"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+
+        when:
+        def result = config.addPackages("java.lang", "java.util")
+
+        then:
+        result.is(config)
+    }
+
+    def "setApplicationContext with null uses PathMatchingResourcePatternResolver"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+
+        when:
+        config.setApplicationContext(null)
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "setApplicationContext without datasource bean sets session context properties"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        ApplicationContext appCtx = Stub(ApplicationContext) {
+            containsBean("dataSource") >> false
+            getClassLoader() >> null
+        }
+
+        when:
+        config.setApplicationContext(appCtx)
+
+        then:
+        config.getProperties().containsKey("hibernate.current_session_context_class")
+        config.getProperties().containsKey("hibernate.bytecode.allow_enhancement_as_proxy")
+        config.getProperties().containsKey("hibernate.bytecode.enhancement_metadata_cache")
+        config.getProperties().containsKey("hibernate.enhancer.enableLazyInitialization")
+        config.getProperties().containsKey("hibernate.enhancer.enableDirtyTracking")
+        config.getProperties().containsKey("hibernate.enhancer.enableAssociationManagement")
+        !config.getProperties().containsKey(JdbcSettings.JAKARTA_NON_JTA_DATASOURCE)
+    }
+
+    def "setApplicationContext with datasource bean injects the datasource into properties"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        DataSource ds = Stub(DataSource)
+        ApplicationContext appCtx = Stub(ApplicationContext) {
+            containsBean("dataSource") >> true
+            getBean("dataSource") >> ds
+            getClassLoader() >> null
+        }
+
+        when:
+        config.setApplicationContext(appCtx)
+
+        then:
+        config.getProperties().get(JdbcSettings.JAKARTA_NON_JTA_DATASOURCE).is(ds)
+    }
+
+    def "setApplicationContext with classLoader sets classloaders property"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        ClassLoader cl = new URLClassLoader([] as URL[], Thread.currentThread().contextClassLoader)
+        ApplicationContext appCtx = Stub(ApplicationContext) {
+            containsBean("dataSource") >> false
+            getClassLoader() >> cl
+        }
+
+        when:
+        config.setApplicationContext(appCtx)
+
+        then:
+        config.getProperties().get(AvailableSettings.CLASSLOADERS).is(cl)
+    }
+
+    def "setApplicationContext when datasource property already set does not overwrite it"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        DataSource existingDs = Stub(DataSource)
+        config.getProperties().put(JdbcSettings.JAKARTA_NON_JTA_DATASOURCE, existingDs)
+        DataSource anotherDs = Stub(DataSource)
+        ApplicationContext appCtx = Stub(ApplicationContext) {
+            containsBean("dataSource") >> true
+            getBean("dataSource") >> anotherDs
+            getClassLoader() >> null
+        }
+
+        when:
+        config.setApplicationContext(appCtx)
+
+        then:
+        config.getProperties().get(JdbcSettings.JAKARTA_NON_JTA_DATASOURCE).is(existingDs)
+    }
+
+    def "setApplicationContext with non-default dataSourceName uses correct bean name"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        config.setDataSourceName("secondary")
+        DataSource ds = Stub(DataSource)
+        ApplicationContext appCtx = Stub(ApplicationContext) {
+            containsBean("dataSource_secondary") >> true
+            getBean("dataSource_secondary") >> ds
+            getClassLoader() >> null
+        }
+
+        when:
+        config.setApplicationContext(appCtx)
+
+        then:
+        config.getProperties().get(JdbcSettings.JAKARTA_NON_JTA_DATASOURCE).is(ds)
+    }
+
+    def "setDataSourceConnectionSource sets dataSourceName, DataSource, and classLoader"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        DataSource ds = Stub(DataSource)
+        ConnectionSource<DataSource, DataSourceSettings> connSrc = Stub(ConnectionSource) {
+            getName() >> "secondary"
+            getSource() >> ds
+        }
+
+        when:
+        config.setDataSourceConnectionSource(connSrc)
+
+        then:
+        config.dataSourceName == "secondary"
+        config.getProperties().get(JdbcSettings.JAKARTA_NON_JTA_DATASOURCE).is(ds)
+        config.getProperties().containsKey("hibernate.current_session_context_class")
+        config.getProperties().containsKey(AvailableSettings.CLASSLOADERS)
+    }
+
+    def "createBootstrapServiceRegistryBuilder returns a non-null builder"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+
+        when:
+        def builder = config.createBootstrapServiceRegistryBuilder()
+
+        then:
+        builder instanceof BootstrapServiceRegistryBuilder
+    }
+
+    def "createStandardServiceRegistryBuilder returns a non-null builder"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        BootstrapServiceRegistry bsr = new BootstrapServiceRegistryBuilder().build()
+
+        when:
+        def builder = config.createStandardServiceRegistryBuilder(bsr)
+
+        then:
+        builder instanceof StandardServiceRegistryBuilder
+
+        cleanup:
+        bsr.close()
+    }
+
+    def "matchesFilter returns false for a non-annotated class"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        def resolver = new PathMatchingResourcePatternResolver()
+        def readerFactory = new CachingMetadataReaderFactory(resolver)
+        def resources = resolver.getResources("classpath:org/grails/orm/hibernate/cfg/NamingStrategyProvider.class")
+
+        when:
+        boolean matched = false
+        for (def resource : resources) {
+            if (resource.readable) {
+                def reader = readerFactory.getMetadataReader(resource)
+                matched = config.matchesFilter(reader, readerFactory)
+                break
+            }
+        }
+
+        then:
+        !matched
+    }
+
+    def "matchesFilter returns true for an @jakarta.persistence.Entity annotated class"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+        def resolver = new PathMatchingResourcePatternResolver()
+        def readerFactory = new CachingMetadataReaderFactory(resolver)
+        def resources = resolver.getResources("classpath*:org/grails/orm/hibernate/cfg/CfgJpaTestEntity.class")
+
+        when:
+        boolean matched = false
+        for (def resource : resources) {
+            if (resource.readable) {
+                def reader = readerFactory.getMetadataReader(resource)
+                matched = config.matchesFilter(reader, readerFactory)
+                break
+            }
+        }
+
+        then:
+        matched
+    }
+
+    def "scanPackages on a package with no annotated classes throws no exception"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+
+        when:
+        config.scanPackages("java.io")
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "scanPackages discovers @Entity annotated domain classes and calls addAnnotatedClasses"() {
+        given:
+        def config = new HibernateMappingContextConfiguration()
+
+        when:
+        config.scanPackages("org.grails.orm.hibernate.cfg")
+
+        then:
+        noExceptionThrown()
+    }
+}
+
+class HibernateMappingContextConfigurationIntegrationSpec extends HibernateGormDatastoreSpec {
+
+    def setupSpec() {
+        manager.addAllDomainClasses([HmccTestBook, HmccTestAuthor])
+    }
+
+    def "buildSessionFactory produces a working session factory via HibernateDatastore"() {
+        expect:
+        sessionFactory != null
+        !sessionFactory.isClosed()
+    }
+
+    def "getServiceRegistry is non-null after the session factory is built"() {
+        expect:
+        datastore.sessionFactory != null
+    }
+
+    def "HibernateDatastore mappingContext is a HibernateMappingContext with registered entities"() {
+        when:
+        def ctx = mappingContext
+
+        then:
+        ctx instanceof HibernateMappingContext
+        ctx.getPersistentEntity(HmccTestBook.name) != null
+        ctx.getPersistentEntity(HmccTestAuthor.name) != null
+    }
+
+    def "HibernateMappingContextConfiguration addAnnotatedClasses is used by buildSessionFactory"() {
+        when:
+        def entities = mappingContext.persistentEntities
+
+        then:
+        !entities.isEmpty()
+    }
+}
+
+@Entity
+class HmccTestBook implements HibernateEntity<HmccTestBook> {
+    String title
+    HmccTestAuthor author
+    static belongsTo = [author: HmccTestAuthor]
+}
+
+@Entity
+class HmccTestAuthor implements HibernateEntity<HmccTestAuthor> {
+    String name
+    static hasMany = [books: HmccTestBook]
+}
+
+@jakarta.persistence.Entity
+class CfgJpaTestEntity {
+    @jakarta.persistence.Id
+    Long id
+    String name
 }
diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/query/MutationQueryDelegateSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/query/MutationQueryDelegateSpec.groovy
new file mode 100644
index 0000000000..c82df13cee
--- /dev/null
+++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/query/MutationQueryDelegateSpec.groovy
@@ -0,0 +1,220 @@
+/*
+ *  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.grails.orm.hibernate.query
+
+import grails.gorm.specs.HibernateGormDatastoreSpec
+import grails.persistence.Entity
+import org.hibernate.query.MutationQuery
+import org.hibernate.query.QueryFlushMode
+
+class MutationQueryDelegateSpec extends HibernateGormDatastoreSpec {
+
+    void setupSpec() {
+        manager.addAllDomainClasses([MutationQueryDelegateTestBook])
+    }
+
+    void setup() {
+        MutationQueryDelegateTestBook.withTransaction {
+            new MutationQueryDelegateTestBook(title: "Book One",   pages: 100).save(flush: true, failOnError: true)
+            new MutationQueryDelegateTestBook(title: "Book Two",   pages: 200).save(flush: true, failOnError: true)
+            new MutationQueryDelegateTestBook(title: "Book Three", pages: 300).save(flush: true, failOnError: true)
+        }
+    }
+
+    void cleanup() {
+        MutationQueryDelegateTestBook.withTransaction {
+            MutationQueryDelegateTestBook.executeUpdate("DELETE FROM MutationQueryDelegateTestBook")
+        }
+    }
+
+    private MutationQuery buildMutationQuery(String hql) {
+        sessionFactory.currentSession.createMutationQuery(hql)
+    }
+
+    void "constructor wraps the MutationQuery"() {
+        given:
+        MutationQueryDelegateTestBook.withTransaction {
+            MutationQuery mq = buildMutationQuery(
+                "UPDATE MutationQueryDelegateTestBook SET title = :title WHERE title = :old"
+            )
+            MutationQueryDelegate delegate = new MutationQueryDelegate(mq)
+
+            expect:
+            delegate != null
+        }
+    }
+
+    void "setTimeout delegates to the underlying MutationQuery"() {
+        when:
+        MutationQueryDelegateTestBook.withTransaction {
+            MutationQuery mq = buildMutationQuery(
+                "UPDATE MutationQueryDelegateTestBook SET pages = 0 WHERE pages > 0"
+            )
+            MutationQueryDelegate delegate = new MutationQueryDelegate(mq)
+            delegate.setTimeout(30)
+        }
+
+        then:
+        noExceptionThrown()
+    }
+
+    void "setQueryFlushMode delegates to the underlying MutationQuery"() {
+        when:
+        MutationQueryDelegateTestBook.withTransaction {
+            MutationQuery mq = buildMutationQuery(
+                "UPDATE MutationQueryDelegateTestBook SET pages = 0 WHERE pages > 0"
+            )
+            MutationQueryDelegate delegate = new MutationQueryDelegate(mq)
+            delegate.setQueryFlushMode(QueryFlushMode.MANUAL)
+        }
+
+        then:
+        noExceptionThrown()
+    }
+
+    void "setParameter by name delegates and executeUpdate returns affected count"() {
+        when:
+        int count = MutationQueryDelegateTestBook.withTransaction {
+            MutationQuery mq = buildMutationQuery(
+                "UPDATE MutationQueryDelegateTestBook SET pages = :newPages WHERE title = :title"
+            )
+            MutationQueryDelegate delegate = new MutationQueryDelegate(mq)
+            delegate.setParameter("newPages", 999)
+            delegate.setParameter("title", "Book One")
+            delegate.executeUpdate()
+        } as int
+
+        then:
+        count == 1
+    }
+
+    void "setParameter by name with type delegates to MutationQuery"() {
+        when:
+        int count = MutationQueryDelegateTestBook.withTransaction {
+            MutationQuery mq = buildMutationQuery(
+                "UPDATE MutationQueryDelegateTestBook SET pages = :newPages WHERE title = :title"
+            )
+            MutationQueryDelegate delegate = new MutationQueryDelegate(mq)
+            delegate.setParameter("newPages", Integer.valueOf(42), Integer.class)
+            delegate.setParameter("title", "Book Two", String.class)
+            delegate.executeUpdate()
+        } as int
+
+        then:
+        count == 1
+    }
+
+    void "setParameter by position delegates to MutationQuery"() {
+        when:
+        int count = MutationQueryDelegateTestBook.withTransaction {
+            MutationQuery mq = buildMutationQuery(
+                "UPDATE MutationQueryDelegateTestBook SET pages = ?1 WHERE title = ?2"
+            )
+            MutationQueryDelegate delegate = new MutationQueryDelegate(mq)
+            delegate.setParameter(1, 77)
+            delegate.setParameter(2, "Book Three")
+            delegate.executeUpdate()
+        } as int
+
+        then:
+        count == 1
+    }
+
+    void "setParameter by position with type delegates to MutationQuery"() {
+        when:
+        int count = MutationQueryDelegateTestBook.withTransaction {
+            MutationQuery mq = buildMutationQuery(
+                "UPDATE MutationQueryDelegateTestBook SET pages = ?1 WHERE title = ?2"
+            )
+            MutationQueryDelegate delegate = new MutationQueryDelegate(mq)
+            delegate.setParameter(1, Integer.valueOf(88), Integer.class)
+            delegate.setParameter(2, "Book One", String.class)
+            delegate.executeUpdate()
+        } as int
+
+        then:
+        count == 1
+    }
+
+    void "setParameterList with Collection delegates as parameter value"() {
+        when:
+        int count = MutationQueryDelegateTestBook.withTransaction {
+            MutationQuery mq = buildMutationQuery(
+                "DELETE FROM MutationQueryDelegateTestBook WHERE title IN (:titles)"
+            )
+            MutationQueryDelegate delegate = new MutationQueryDelegate(mq)
+            delegate.setParameterList("titles", (Collection<?>) ["Book One", "Book Two"])
+            delegate.executeUpdate()
+        } as int
+
+        then:
+        count == 2
+    }
+
+    void "setParameterList with Object array delegates as parameter value"() {
+        when:
+        int count = MutationQueryDelegateTestBook.withTransaction {
+            MutationQuery mq = buildMutationQuery(
+                "DELETE FROM MutationQueryDelegateTestBook WHERE title IN (:titles)"
+            )
+            MutationQueryDelegate delegate = new MutationQueryDelegate(mq)
+            delegate.setParameterList("titles", ["Book Two", "Book Three"] as Object[])
+            delegate.executeUpdate()
+        } as int
+
+        then:
+        count == 2
+    }
+
+    void "list() throws UnsupportedOperationException"() {
+        when:
+        MutationQueryDelegateTestBook.withTransaction {
+            MutationQuery mq = buildMutationQuery(
+                "UPDATE MutationQueryDelegateTestBook SET pages = 0 WHERE pages > 0"
+            )
+            MutationQueryDelegate delegate = new MutationQueryDelegate(mq)
+            delegate.list()
+        }
+
+        then:
+        thrown(UnsupportedOperationException)
+    }
+
+    void "selectQuery() returns null for mutation queries"() {
+        expect:
+        MutationQueryDelegateTestBook.withTransaction {
+            MutationQuery mq = buildMutationQuery(
+                "UPDATE MutationQueryDelegateTestBook SET pages = 0 WHERE pages > 0"
+            )
+            MutationQueryDelegate delegate = new MutationQueryDelegate(mq)
+            delegate.selectQuery() == null
+        }
+    }
+}
+
+@Entity
+class MutationQueryDelegateTestBook {
+    String title
+    Integer pages
+
+    static constraints = {
+        title nullable: false
+        pages nullable: false
+    }
+}