kotlin 如何使用mockK模拟s3放置对象请求?

qlckcl4x  于 2023-03-19  发布在  Kotlin
关注(0)|答案(1)|浏览(172)

我在下面有一个函数,用于将数据上传到s3

val s3Client = S3Client.create()
    
data class S3Object(
     val bucket: String,
     val key: String,
     val contents: String,
     val contentType: String
)
        
 fun putS3Object(s3Object: S3Object){
     val putObjectRequest = PutObjectRequest.builder()
                        .bucket(s3Object.bucket)
                        .key(s3Object.key.toString())
                        .contentType(s3Object.contentType)
                        .build()
     s3Client.putObject(putObjectRequest, RequestBody.fromString(s3Object.contents))
  }

我嘲笑它如下:

every { S3Client.create()} returns s3Client

val s3Object = S3Object("test-bucket", "/test/key", "test-event", "application/json; charset=UTF-8")
val putRequest = PutObjectRequest.builder()
            .bucket(s3Object.bucket)
            .contentType(s3Object.contentType)
            .key(s3Object.key)
            .build()
val putObjectResponse = PutObjectResponse
            .builder()
            .build()
every{ s3Client.putObject(putObjectRequest, RequestBody.fromString(s3Object.contents))} answers {putObjectResponse}

我正在创建s3 PutObjectRequest和PutObjectResponse作为mock的一部分,但是当我尝试运行测试时,我得到了以下mockK异常:
io.mockk.MockKException:未找到以下项的答案:S3客户端(#4).放置对象(放置对象请求(存储桶=测试存储桶,内容类型=应用/子;字符集=UTF-8,键="/测试/键”),软件。亚马逊。awssdk.core.sync.RequestBody@49322d04)

vxf3dgd4

vxf3dgd41#

首先,对s3Client.putObject(..)的调用是您要测试的吗?发送到该方法的数据是否正确?
如果你把被测类和测试类分开会更容易一些,所以在主代码行中你应该有这样的内容:

import software.amazon.awssdk.core.sync.RequestBody
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.model.PutObjectRequest

data class S3Object(
    val bucket: String,
    val key: String,
    val contents: String,
    val contentType: String,
)

class S3Service(
    // always in inject your dependencies then we can override then you override them in a test
    private val s3Client: S3Client = S3Client.create() 
) {
    fun putS3Object(s3Object: S3Object) {
        val putObjectRequest = PutObjectRequest.builder()
            .bucket(s3Object.bucket)
            .key(s3Object.key.toString())
            .contentType(s3Object.contentType)
            .build()
        s3Client.putObject(putObjectRequest, RequestBody.fromString(s3Object.contents))
    }
}

现在是测试类:

import io.kotest.matchers.shouldBe
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import software.amazon.awssdk.core.sync.RequestBody
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.model.PutObjectRequest
import software.amazon.awssdk.services.s3.model.PutObjectResponse

class S3ServiceTest {

    // you need a mock the client since this is what you want to mock to check the invocation
    private val s3Client = mockk<S3Client>()
    private lateinit var s3Service: S3Service

    @BeforeEach
    fun beforeEach() {
        // its supposedly marginally more efficient to define your mocks once and clear then not instantiate each time
        clearAllMocks()
        // inject the mock s3Client so you do not get the `S3Client.create()` one
        s3Service = S3Service(s3Client)
    }

    @Test
    fun `putObject call correctly formed`() {
        val s3Object = S3Object(
            "test-bucket",
            "/test/key",
            "test-event",
            "application/json; charset=UTF-8",
        )
        every { s3Client.putObject(any<PutObjectRequest>(), any<RequestBody>()) } returns mockk()
        s3Service.putS3Object(s3Object)
    }

现在这是不完整的-它没有Assert任何东西。但这里的要点是,在every { s3Client.putObject(any<PutObjectRequest>(),...行中,我设置Mockk将捕获到putObject()的任何输入。在这种情况下,指示Mockk期望特定的输入是错误的......相反,您希望看到输入是什么,然后测试它(见下文)。
我需要使用<PutObjectRequest>,因为有几个s3Client.putObject(),所以mockk需要知道你在听哪个。
另一个变化是线的末端。你可以这样写:

} answers {putObjectResponse}

这是可以的,但有两件事
1.对于测试,您真的关心putObject()返回什么吗?如果这是更复杂测试的一部分,您可能会关心。如果不关心,请使用returns mock()
1.这里有两种“结尾”可以使用... returns OBJECTanswers { LAMBDA }。第二种情况在你不知道后面返回什么时很有用,在这种情况下你的输出是静态的,因为使用returns OBJECT更有效...
在需要正确返回的情况下,请执行以下操作:

val putObjectResponse = PutObjectResponse
            .builder()
            .build()
        every { s3Client.putObject(any<PutObjectRequest>(), any<RequestBody>()) } returns putObjectResponse

现在我们如何才能真正看到对putObject的调用是否正确呢?为此,您需要一个名为slot的不同特性,您可以这样使用:

@Test
    fun `putObject call correctly formed2`() {
        val s3Object = S3Object(
            "test-bucket",
            "/test/key",
            "test-event",
            "application/json; charset=UTF-8",
        )
        every { s3Client.putObject(any<PutObjectRequest>(), any<RequestBody>()) } returns mockk()

        val pubObjectRequestSlot = slot<PutObjectRequest>()
        every { s3Client.putObject(capture(pubObjectRequestSlot), any<RequestBody>()) } returns mockk()

        s3Service.putS3Object(s3Object)

        pubObjectRequestSlot.captured.bucket() shouldBe "test-bucket"
        pubObjectRequestSlot.captured.key() shouldBe "/test/key"
        // more assertions here...
        // and of course you can capture the RequestBody argument too
    }

相关问题