refactor: migrate :src:protocol:jdbc Groovy tests to Kotlin
diff --git a/src/protocol/jdbc/build.gradle.kts b/src/protocol/jdbc/build.gradle.kts
index 3503b81..702d139 100644
--- a/src/protocol/jdbc/build.gradle.kts
+++ b/src/protocol/jdbc/build.gradle.kts
@@ -31,4 +31,5 @@
     }
 
     testImplementation(testFixtures(projects.src.core))
+    testImplementation("io.mockk:mockk")
 }
diff --git a/src/protocol/jdbc/src/test/kotlin/org/apache/jmeter/protocol/jdbc/AbstractJDBCTestElementExecute.kt b/src/protocol/jdbc/src/test/kotlin/org/apache/jmeter/protocol/jdbc/AbstractJDBCTestElementExecute.kt
new file mode 100644
index 0000000..192415a
--- /dev/null
+++ b/src/protocol/jdbc/src/test/kotlin/org/apache/jmeter/protocol/jdbc/AbstractJDBCTestElementExecute.kt
@@ -0,0 +1,24 @@
+/*
+ * 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
+ *
+ * http://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.jmeter.protocol.jdbc
+
+import org.apache.jmeter.samplers.SampleResult
+import java.sql.Connection
+
+fun AbstractJDBCTestElement.executeForTest(conn: Connection, sampleResult: SampleResult) =
+    execute(conn, sampleResult)
diff --git a/src/protocol/jdbc/src/test/kotlin/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerTest.kt b/src/protocol/jdbc/src/test/kotlin/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerTest.kt
index 0f72eea7..1ab034d 100644
--- a/src/protocol/jdbc/src/test/kotlin/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerTest.kt
+++ b/src/protocol/jdbc/src/test/kotlin/org/apache/jmeter/protocol/jdbc/sampler/JDBCSamplerTest.kt
@@ -17,116 +17,139 @@
 
 package org.apache.jmeter.protocol.jdbc.sampler
 
+import io.mockk.every
+import io.mockk.justRun
+import io.mockk.mockk
+import io.mockk.verifyOrder
+import org.apache.jmeter.config.ConfigTestElement
+import org.apache.jmeter.config.gui.SimpleConfigGui
+import org.apache.jmeter.protocol.jdbc.executeForTest
+import org.apache.jmeter.samplers.SampleResult
+import org.apache.jmeter.testelement.TestElementSchema
+import org.junit.jupiter.api.Assertions.assertArrayEquals
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
 import java.sql.Connection
 import java.sql.ResultSet
 import java.sql.ResultSetMetaData
 import java.sql.SQLException
 import java.sql.Statement
 
-import org.apache.jmeter.config.ConfigTestElement
-import org.apache.jmeter.samplers.SampleResult
-import org.apache.jmeter.testelement.property.JMeterProperty
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+class JDBCSamplerTest {
 
-import spock.lang.Specification
-import spock.lang.Unroll
+    lateinit var sut: JDBCSampler
 
-@Unroll
-class JDBCSamplerSpec extends Specification {
+    @BeforeEach
+    fun setup() {
+        sut = JDBCSampler()
+    }
 
-    def sut = new JDBCSampler()
-
-    def "applies matches SimpleConfigGui"() {
-        given:
-            def mockConfig = Mock(ConfigTestElement)
-            def mockProperty = Mock(JMeterProperty)
-        when:
-            def applies = sut.applies(mockConfig)
-        then:
-            1 * mockConfig.getProperty(_ as String) >> mockProperty
-            1 * mockProperty.getStringValue() >> propertyValue
-            applies == expectedApplies
-            // this check will catch any future additions
-            sut.APPLIABLE_CONFIG_CLASSES.size() == 1
-        where:
-            propertyValue                                  | expectedApplies
-            "org.apache.jmeter.config.gui.SimpleConfigGui" | true
-            "org.apache.jmeter.config.gui.SomethingElse"   | false
+    @Test
+    fun `applies matches SimpleConfigGui`() {
+        val configElement = SimpleConfigGui().createTestElement() as ConfigTestElement
+        assertTrue(
+            sut.applies(configElement),
+            "JDBCSampler should apply to SimpleConfigGui"
+        )
+        configElement[TestElementSchema.guiClass] = "org.apache.jmeter.config.gui.SomethingElse"
+        assertFalse(
+            sut.applies(configElement),
+            "JDBCSampler should not apply to gui.SomethingElse"
+        )
     }
 
     /* AbstractJDBCTestElement tests */
 
-    def "execute with SELECT query"() {
-        given:
-            def conn = Mock(Connection)
-            def sample = Mock(SampleResult)
-            def stmt = Mock(Statement)
-            def rs = Mock(ResultSet)
-            def meta = Mock(ResultSetMetaData)
-            sut.setQuery("SELECT")
-        when:
-            def response = sut.execute(conn, sample)
-        then:
-            1 * conn.createStatement() >> stmt
-            1 * stmt.setQueryTimeout(0)
-            1 * stmt.executeQuery(_ as String) >> rs
-            1 * sample.latencyEnd()
+    @Test
+    fun `execute with SELECT query`() {
+        val meta = mockk<ResultSetMetaData> {
+            every { columnCount } returns 0
+        }
+        val rs = mockk<ResultSet> {
+            every { metaData } returns meta
+            every { close() } throws SQLException()
+            every { next() } returns false
+        }
+        val stmt = mockk<Statement> {
+            every { executeQuery(any()) } returns rs
+            justRun { queryTimeout = any() }
+            justRun { close() }
+        }
+        val conn = mockk<Connection> {
+            every { createStatement() } returns stmt
+        }
+        val sample = mockk<SampleResult> {
+            justRun { latencyEnd() }
+        }
+
+        sut.query = "SELECT"
+        val response = sut.executeForTest(conn, sample)
+
+        verifyOrder {
+            conn.createStatement()
+            stmt.queryTimeout = 0
+            stmt.executeQuery(any())
+            sample.latencyEnd()
             // getStringFromResultSet
-            1 * rs.getMetaData() >> meta
-            1 * rs.next()
-            1 * rs.close() >> { throw new SQLException() }
-            1 * stmt.close()
-            // 1 * conn.close() // closed by JDBCSampler
-            1 * meta.getColumnCount() >> 0
-            response == [] as byte[]
+            rs.metaData
+            meta.columnCount
+            rs.next()
+            rs.close()
+            stmt.close()
+            // conn.close() // closed by JDBCSampler
+        }
+        assertArrayEquals(byteArrayOf(), response, "response")
     }
 
-    def "Catches SQLException during Connection closing"() {
-        given:
-            def mockConnection = Mock(Connection)
-        when:
-            sut.close(mockConnection)
-        then:
-            1 * mockConnection.close() >> { throw new SQLException() }
-            noExceptionThrown()
+    @Test
+    fun `Catches SQLException during Connection closing`() {
+        val mockConnection = mockk<Connection> {
+            every { close() } throws SQLException()
+        }
+        JDBCSampler.close(mockConnection)
     }
 
-    def "Catches SQLException during Statement closing"() {
-        given:
-            def mockStatement = Mock(Statement)
-        when:
-            sut.close(mockStatement)
-        then:
-            1 * mockStatement.close() >> { throw new SQLException() }
-            noExceptionThrown()
+    @Test
+    fun `Catches SQLException during Statement closing`() {
+        val mockStatement = mockk<Statement> {
+            every { close() } throws SQLException()
+        }
+        JDBCSampler.close(mockStatement)
     }
 
-    def "Catches SQLException during ResultSet closing"() {
-        given:
-            def mockResultSet = Mock(ResultSet)
-        when:
-            sut.close(mockResultSet)
-        then:
-            1 * mockResultSet.close() >> { throw new SQLException() }
-            noExceptionThrown()
+    @Test
+    fun `Catches SQLException during ResultSet closing`() {
+        val mockStatement = mockk<ResultSet> {
+            every { close() } throws SQLException()
+        }
+        JDBCSampler.close(mockStatement)
     }
 
-    def "getIntegerQueryTimeout returns #expectedTimeout from #initialTimeout"() {
-        given:
-            sut.setQueryTimeout(initialTimeout)
-        when:
-            def timeout = sut.getIntegerQueryTimeout()
-        then:
-            timeout == expectedTimeout
-        where:
-            initialTimeout | expectedTimeout
-            "0"            | 0
-            "1"            | 1
-            "2147483647"   | Integer.MAX_VALUE
-            "-1"           | -1
-            "-2147483648"  | Integer.MIN_VALUE
-            "2147483648"   | 0 // max int + 1
-            "-2147483649"  | 0 // min int - 1
-            "nan"          | 0
-            ""             | 0
+    data class GetIntegerQueryTimeoutCase(val initialTimeout: String, val expectedTimeout: Int, val message: String? = null)
+
+    @ParameterizedTest
+    @MethodSource("getIntegerQueryTimeoutCases")
+    fun `getIntegerQueryTimeout returns #expectedTimeout from #initialTimeout`(case: GetIntegerQueryTimeoutCase) {
+        sut.queryTimeout = case.initialTimeout
+        assertEquals(case.expectedTimeout, sut.getIntegerQueryTimeout(), case.message)
     }
+
+    fun getIntegerQueryTimeoutCases() = listOf(
+        GetIntegerQueryTimeoutCase("0", 0),
+        GetIntegerQueryTimeoutCase("1", 1),
+        GetIntegerQueryTimeoutCase("2147483647", Integer.MAX_VALUE),
+        GetIntegerQueryTimeoutCase("-1", -1),
+        GetIntegerQueryTimeoutCase("-2147483648", Integer.MIN_VALUE),
+        GetIntegerQueryTimeoutCase("2147483648", 0, "max int + 1"),
+        GetIntegerQueryTimeoutCase("-2147483649", 0, "min int - 1"),
+        GetIntegerQueryTimeoutCase("nan", 0),
+        GetIntegerQueryTimeoutCase("", 0),
+    )
 }