Search code examples
kotlinktorktor-client

Ktor API test fails to serialize class to HTTP body despite using ContentNegotiation in Kotlin


I am building a server using the Ktor framework in Kotlin. I am currently writing a test for my API route that registers a user, but I keep encountering an error when I attempt to send a JSON body to the server. The error message indicates that serialization fails when trying to serialize the class to the HTTP body.

I followed the Ktor testing documentation and learned that I need to install ContentNegotiation in my test client. However, despite doing this, the error persists.

Here is my current code:

@Serializable
data class User(val email: String, val password: String)


fun ApplicationTestBuilder.createEnvironment() {
        val fakeUserRepository = FakeUserRepository()
        val jwtUserService = FakeJWTUserService(fakeUserRepository)
        val fakeUserService = FakeUserService(fakeUserRepository, jwtUserService)

        application {
            configureSerialization()
            configureRouting(fakeUserService)
        }
    }

    @Test
    fun testRegister() = testApplication {
        createEnvironment()

        val client = createClient {
            install(ContentNegotiation) {
                json()
            }
        }

        val validUser = User(
            "[email protected]",
            "Simone_2006",
        )

        client.post("users/register") {
            contentType(ContentType.Application.Json)
            setBody(validUser)
        }.apply {
            assertEquals(HttpStatusCode.OK, status)
        }
    }

However, the code fails to work as expected. Here is the error message:

fun <P : Pipeline<*, ApplicationCall>, B : Any, F : Any> install(plugin: Plugin<P, B, F>, configure: B.() -> Unit = ...): Unit' cannot be called in this context with an implicit receiver. Use an explicit receiver if necessary.

I also tried moving the install(ContentNegotiation) block outside the createClient function, as shown below:

fun ApplicationTestBuilder.createEnvironment() {
        val fakeUserRepository = FakeUserRepository()
        val jwtUserService = FakeJWTUserService(fakeUserRepository)
        val fakeUserService = FakeUserService(fakeUserRepository, jwtUserService)

        application {
            configureSerialization()
            configureRouting(fakeUserService)
        }
    }

    @Test
    fun testRegister() = testApplication {
        createEnvironment()
        install(ContentNegotiation) {
            json()
        }

        val validUser = User(
            "[email protected]",
            "Simone_2006",
        )

        client.post("users/register") {
            contentType(ContentType.Application.Json)
            setBody(validUser)
        }.apply {
            assertEquals(HttpStatusCode.OK, status)
        }
    }

Despite trying both approaches, I still get an error message:

Fail to prepare request body for sending. 
The body type is: class com.example.user.model.User, with Content-Type: application/json.

If you expect serialized body, please check that you have installed the corresponding plugin(like `ContentNegotiation`) and set `Content-Type` header.
java.lang.IllegalStateException: Fail to prepare request body for sending. 
The body type is: class com.example.user.model.User, with Content-Type: application/json.

If you expect serialized body, please check that you have installed the corresponding plugin(like ContentNegotiation) and set Content-Type header.

and the JSON serialization doesn't seem to work. I also verified that the configureSerialization() method is correctly applied to my application, but I'm unsure if the issue lies there.

Here is whole code:

package com.example

import com.example.plugins.configureRouting
import com.example.plugins.configureSerialization
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.testing.*
import kotlin.test.*

data class User(val email: String, val password: String)

class ApplicationTest {
    fun ApplicationTestBuilder.createEnvironment() {
        application {
            configureRouting()
            configureSerialization()
        }
    }

    @Test
    fun testRegister() = testApplication {
        createEnvironment()

        val client = createClient {
            install(ContentNegotiation) {
                json()
            }
        }

        val validUser = User(
            "[email protected]",
            "Simone_2006",
        )

        client.post("/users/register") {
            contentType(ContentType.Application.Json)
            setBody(validUser)
        }
    }
}

I couldn't find similar issues online, so I would appreciate any help in resolving this.


Solution

  • The problem is that the server ContentNegotiation plugin is installed into the test client because you have the following import:

    import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
    

    To solve the problem, you need to import the client ContentNegotiation plugin:

    import io.ktor.client.plugins.contentnegotiation.ContentNegotiation