1

I'm trying to create a client which I use to test my controller

The controller

@Secured(SecurityRule.IS_AUTHENTICATED)
@Controller
class InjuryController(private val userService: UserService, private val injuryService: InjuryService) {
...
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Post("/injuries/{id}/images")
    fun postImage(id: Long, file: CompletedFileUpload, principal: Principal): HttpResponse<*>? {
        ...
        return HttpResponse.ok(imageReference)
    }
...
}

The client

@Client("/")
interface InjuryClient {
    @Post("/injuries/{id}/images", produces = [MULTIPART_FORM_DATA])
    fun postImage(id: Long, body: MultipartBody, @Header authorization: String): ImageReference
}

The test

@Test
fun `Post an image an injury`() {
    // Given
    val description = "description"
    val occurredAt = LocalDateTime.now()
    val id = createInjury(description, occurredAt).id

    val toWrite = "test file"
    val file = File.createTempFile("data", ".txt")
    FileWriter(file).apply {
        write(toWrite)
        close()
    }

    val requestBody = MultipartBody.builder()
            .addPart("data",
                    file.name,
                    MediaType.TEXT_PLAIN_TYPE,
                    file
            ).build()

    // When
    val response = injuryClient.postImage(id, requestBody, authorization)

    // Then
    assertEquals("$id:${file.name}", response.key)
}

The error

The type java.util.LinkedHashMap is not a supported type for a multipart request body
io.micronaut.http.multipart.MultipartException: The type java.util.LinkedHashMap is not a supported type for a multipart request body
    at io.micronaut.http.client.DefaultHttpClient.buildMultipartRequest(DefaultHttpClient.java:2063)
    at io.micronaut.http.client.DefaultHttpClient.buildNettyRequest(DefaultHttpClient.java:1480)
    at io.micronaut.http.client.DefaultHttpClient.sendRequestThroughChannel(DefaultHttpClient.java:1599)
    at io.micronaut.http.client.DefaultHttpClient.lambda$null$27(DefaultHttpClient.java:1035)
    at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:577)
    at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:570)
    at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:549)
    at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:490)
    at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:615)
    at io.netty.util.concurrent.DefaultPromise.setSuccess0(DefaultPromise.java:604)
    at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:104)
    at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:84)
    at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.fulfillConnectPromise(AbstractNioChannel.java:300)
    at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:335)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:688)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:635)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:552)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:514)
    at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1044)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:834)

Any clue about how to get rid of that error?

user672009
  • 4,379
  • 8
  • 44
  • 77

1 Answers1

1

The client assumes method arguments are members of the body instead of each one representing the entire body. You can achieve the desired behavior by annotating the body argument in the client with @Body

James Kleeh
  • 12,094
  • 5
  • 34
  • 61
  • Thanks! Also worth mentioning is that the part name, which in the above question is named "data", has to match the name of the argument in the controller method. So it only worked after I changed that to "file" – user672009 Dec 13 '19 at 03:05
  • @user672009 You can use `@Part("file")` if you want to keep the method argument name the same – James Kleeh Dec 13 '19 at 17:02