Repository: tipsy/javalin (kotlin)

Issue:
Testing request with mockk using custom body results in ClassCastException.
**Actual behavior (the bug)**
When trying to test Javalin API using Mockk with a request that uses bodyAsClass() throws ClassCastException

Test file
```kt
    @Test
    @Order(1)
    fun `create user with invalid short username`() {
        every {
            ctx.body()
        } returns jacksonObjectMapper().writeValueAsString(UserCreateBody("d", UserConstants.email, UserConstants.password))

        uc.createUser(ctx) // Line 50

        verify {
            ctx.status(201)
        }
    }
```
UserController (uc)
```kt
fun createUser(ctx: Context) {
        // ... 
        val body = ctx.bodyAsClass<UserCreateBody>() // This is what breaks (Line 45)
       // ...
}
```


Full Exception:

```kt
class java.lang.Object cannot be cast to class app.lightable.arc.domain.body.User.UserCreateBody (java.lang.Object is in module java.base of loader 'bootstrap'; app.lightable.arc.domain.body.User.UserCreateBody is in unnamed module of loader 'app')
java.lang.ClassCastException: class java.lang.Object cannot be cast to class app.lightable.arc.domain.body.User.UserCreateBody (java.lang.Object is in module java.base of loader 'bootstrap'; app.lightable.arc.domain.body.User.UserCreateBody is in unnamed module of loader 'app')
	at app.lightable.arc.domain.controllers.UserController.createUser(UserController.kt:45)
	at UserControllerTest.create user with invalid short username(UserControllerTest.kt:50)
```

**Expected behavior**
You shouldn't get a ClassCastException

**To Reproduce**

RequestBody.kt
```kt
data class ExampleBody(val myData: String)
```

ExampleController.kt
```kt
class ExampleController {
    fun thisWillBreak(ctx: Context) {
        val body = ctx.bodyAsClass<ExampleBody>()

        println("Data is ${body.myData}")

        ctx.status(200)
        return
    }
}
```

ExampleControllerTest.kt
```kt
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
class ExampleControllerTest {
    private val ctx = mockk<Context>(relaxed = true)
    
    private val ec: ExampleController = ExampleController()

    @Test
    @Order(1)
    fun `this test will break`() {
        every {
            ctx.body()
        } returns jacksonObjectMapper().writeValueAsString(ExampleBody("this is my data"))

        ec.thisWillBreak(ctx)

        verify {
            ctx.status(200)
        }
    }
}
```

**Additional context**
- Javalin/testtools 5.6.1
- Mockk 1.13.5
- Assertj 3.24.2

This is a complete shot in the dark but it could it possibly be related to the fact it's using a data class? I'm not entirely sure, any help would be greatly appreciated. If there is a better way to do this, or a way that doesn't break everything please also update the [article](https://javalin.io/tutorials/testing) for implmenting testing using mockk. Thank you!

Pull request:
Add Mockk test case based on reported issue to our mocking scenarios. 

Resolves #1953

Patch:
diff --git a/javalin/pom.xml b/javalin/pom.xml
index 6d9074035..45e3357af 100644
--- a/javalin/pom.xml
+++ b/javalin/pom.xml
@@ -91,6 +91,12 @@
             <version>${mockito.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>io.mockk</groupId>
+            <artifactId>mockk-jvm</artifactId>
+            <version>${mockk.version}</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>com.konghq</groupId>
             <artifactId>unirest-java</artifactId>
diff --git a/pom.xml b/pom.xml
index a1e012251..8331379e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,8 +97,9 @@
         <assertj.version>3.23.1</assertj.version>
         <moshi.version>1.13.0</moshi.version>
         <java.websocket.version>1.5.2</java.websocket.version>
-        <junit.version>5.7.2</junit.version>
-        <mockito.version>4.6.1</mockito.version>
+        <junit.version>5.9.3</junit.version>
+        <mockito.version>5.4.0</mockito.version>
+        <mockk.version>1.13.5</mockk.version>
         <unirest.version>3.13.10</unirest.version>
         <selenium.chrome.driver.version>4.9.1</selenium.chrome.driver.version>
         <webdrivermanager.version>5.3.3</webdrivermanager.version>


Test patch:
diff --git a/javalin/src/test/java/io/javalin/TestMocking.kt b/javalin/src/test/java/io/javalin/TestMocking.kt
new file mode 100644
index 000000000..449f89482
--- /dev/null
+++ b/javalin/src/test/java/io/javalin/TestMocking.kt
@@ -0,0 +1,75 @@
+package io.javalin
+
+import io.javalin.http.Context
+import io.javalin.http.HttpStatus
+import java.util.concurrent.atomic.AtomicReference
+import org.junit.jupiter.api.Test
+import io.javalin.http.HttpStatus.IM_A_TEAPOT
+import io.javalin.http.HttpStatus.OK
+import io.javalin.http.bodyAsClass
+import io.javalin.json.JavalinJackson
+import io.mockk.every
+import org.assertj.core.api.AssertionsForClassTypes.assertThat
+import org.junit.jupiter.api.Assertions.assertDoesNotThrow
+import org.junit.jupiter.api.Nested
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+import io.mockk.mockk
+import java.lang.reflect.Type
+
+internal class TestMocking {
+
+    @Nested
+    inner class Mockito {
+
+        @Test
+        fun `should create mock of context from interface`() {
+            val context = assertDoesNotThrow { mock(Context::class.java) }
+            assertThat(context).isNotNull()
+        }
+
+        @Test
+        fun `should mock request and response based methods`() {
+            val context = mock(Context::class.java)
+            val status = AtomicReference(OK)
+
+            `when`(context.status(any(HttpStatus::class.java))).then {
+                status.set(it.getArgument(0, HttpStatus::class.java))
+                context
+            }
+            `when`(context.status()).then { status.get() }
+
+            assertThat(context.status()).isEqualTo(OK)
+            context.status(IM_A_TEAPOT)
+            assertThat(context.status()).isEqualTo(IM_A_TEAPOT)
+        }
+
+        // Pass "any()" value mock to non-nullable argument (workaround for Kotlin)
+        private fun <T : Any?> any(type: Class<T>): T = org.mockito.Mockito.any(type)
+
+    }
+
+    @Nested
+    inner class Mockk {
+
+        @Test
+        fun `should create context mockk`() {
+            val context = mockk<Context>()
+            assertThat(context).isNotNull()
+        }
+
+        @Test
+        fun `gh-1953 should mock body`() {
+            data class Test(val message: String)
+
+            val context = mockk<Context>()
+            every { context.body() } returns """{"message":"Hello"}"""
+            every { context.bodyAsClass<Test>(Test::class.java as Type) } answers { JavalinJackson.defaultMapper().readValue(context.body(), Test::class.java) }
+
+            val body = context.bodyAsClass<Test>()
+            assertThat(body.message).isEqualTo("Hello")
+        }
+
+    }
+
+}
diff --git a/javalin/src/test/java/io/javalin/TestMockito.java b/javalin/src/test/java/io/javalin/TestMockito.java
deleted file mode 100644
index a81000a69..000000000
--- a/javalin/src/test/java/io/javalin/TestMockito.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package io.javalin;
-
-import io.javalin.http.Context;
-import io.javalin.http.HttpStatus;
-import java.util.concurrent.atomic.AtomicReference;
-import org.junit.jupiter.api.Test;
-import static io.javalin.http.HttpStatus.IM_A_TEAPOT;
-import static io.javalin.http.HttpStatus.OK;
-import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class TestMockito {
-
-    @Test
-    public void shouldCreateMockOfContextFromInterface() {
-        Context context = assertDoesNotThrow(() -> mock(Context.class));
-        assertThat(context).isNotNull();
-    }
-
-    @Test
-    public void shouldMockRequestAndResponseBasedMethods() {
-        Context context = mock(Context.class);
-
-        AtomicReference<HttpStatus> status = new AtomicReference<>(OK);
-        when(context.status(any(HttpStatus.class))).then(value -> {
-            status.set(value.getArgument(0, HttpStatus.class));
-            return context;
-        });
-        when(context.status()).then((invocationOnMock) -> status.get());
-
-        assertThat(context.status()).isEqualTo(OK);
-        context.status(IM_A_TEAPOT);
-        assertThat(context.status()).isEqualTo(IM_A_TEAPOT);
-    }
-
-}