aSe*_*emy 10 unit-testing mockito kotlin spring-boot junit5
在 Spring Boot 单元测试中,如何模拟 @ConstructorBinding @ConfigurationProperties 数据类?
我想用不同的配置测试 FtpService (a @Service,其中有 a )。RestTemplate
FtpService 的属性来自 Kotlin 数据类 - UrlProperties - 用ConstructorBinding和注释@ConfigurationProperties。
注意:FtpService 的构造函数从 UrlProperties 中提取属性。这意味着在Spring 加载 FtpService之前必须对 UrlProperties 进行模拟和存根
当我尝试模拟 UrlProperties 以便为不同的测试设置属性时,我要么收到错误,要么无法插入 bean
Cannot bind @ConfigurationProperties for bean 'urlProperties'. Ensure that @ConstructorBinding has not been applied to regular bean
Run Code Online (Sandbox Code Playgroud)
Cannot bind @ConfigurationProperties for bean 'urlProperties'. Ensure that @ConstructorBinding has not been applied to regular bean
Run Code Online (Sandbox Code Playgroud)
唯一的“解决方法”是手动定义所有 bean(这意味着我在测试过程中错过了 Spring Boot 的魔力),在我看来这更令人困惑。
解决方法 - 手动重新定义每个测试 | `src/test/kotlin/com/example/FtpServiceTest2.kt`
import com.example.service.FtpService
import com.example.service.UrlProperties
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.context.annotation.Bean
import org.springframework.test.context.ContextConfiguration
@TestConfiguration
@SpringBootTest(classes = [FtpService::class])
@AutoConfigureWebClient(registerRestTemplate = true)
class FtpServiceTest
@Autowired constructor(
private val ftpService: FtpService
) {
// MockBean inserted into Spring Context too late,
// FtpService constructor throws NPE
// @MockBean
// lateinit var urlProperties: UrlProperties
@ContextConfiguration
class MyTestContext {
// error -
// > Cannot bind @ConfigurationProperties for bean 'urlProperties'.
// > Ensure that @ConstructorBinding has not been applied to regular bean
var urlProperties: UrlProperties = mock(UrlProperties::class.java)
@Bean
fun urlProperties() = urlProperties
// error -
// > Cannot bind @ConfigurationProperties for bean 'urlProperties'.
// > Ensure that @ConstructorBinding has not been applied to regular bean
// @Bean
// fun urlProperties(): UrlProperties {
// return UrlProperties(
// UrlProperties.FtpProperties(
// url = "ftp://localhost:21"
// ))
// }
}
@Test
fun `test fetch file root`() {
`when`(MyTestContext().urlProperties.ftp)
.thenReturn(UrlProperties.FtpProperties(
url = "ftp://localhost:21"
))
assertEquals("I'm fetching a file from ftp://localhost:21!",
ftpService.fetchFile())
}
@Test
fun `test fetch file folder`() {
`when`(MyTestContext().urlProperties.ftp)
.thenReturn(UrlProperties.FtpProperties(
url = "ftp://localhost:21/user/folder"
))
assertEquals("I'm fetching a file from ftp://localhost:21/user/folder!",
ftpService.fetchFile())
}
}
Run Code Online (Sandbox Code Playgroud)
春季应用 | `src/main/kotlin/com/example/MyApp.kt`
import com.example.service.FtpService
import com.example.service.UrlProperties
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.springframework.boot.web.client.RestTemplateBuilder
class FtpServiceTest2 {
private val restTemplate =
RestTemplateBuilder()
.build()
private lateinit var ftpService: FtpService
private lateinit var urlProperties: UrlProperties
@BeforeEach
fun beforeEachTest() {
urlProperties = mock(UrlProperties::class.java)
`when`(urlProperties.ftp)
.thenReturn(UrlProperties.FtpProperties(
url = "default"
))
ftpService = FtpService(restTemplate, urlProperties)
}
/** this is the only test that allows me to redefine 'url' */
@Test
fun `test fetch file folder - redefine`() {
urlProperties = mock(UrlProperties::class.java)
`when`(urlProperties.ftp)
.thenReturn(UrlProperties.FtpProperties(
url = "ftp://localhost:21/redefine"
))
// redefine the service
ftpService = FtpService(restTemplate, urlProperties)
assertEquals("I'm fetching a file from ftp://localhost:21/redefine!",
ftpService.fetchFile())
}
@Test
fun `test default`() {
assertEquals("I'm fetching a file from default!",
ftpService.fetchFile())
}
@Test
fun `test fetch file root`() {
`when`(urlProperties.ftp)
.thenReturn(UrlProperties.FtpProperties(
url = "ftp://localhost:21"
))
assertEquals("I'm fetching a file from ftp://localhost:21!",
ftpService.fetchFile())
}
@Test
fun `test fetch file folder`() {
doReturn(
UrlProperties.FtpProperties(
url = "ftp://localhost:21/user/folder"
)).`when`(urlProperties).ftp
assertEquals("I'm fetching a file from ftp://localhost:21/user/folder!",
ftpService.fetchFile())
}
@Test
fun `test fetch file folder - reset`() {
Mockito.reset(urlProperties)
`when`(urlProperties.ftp)
.thenReturn(UrlProperties.FtpProperties(
url = "ftp://localhost:21/mockito/reset/when"
))
assertEquals("I'm fetching a file from ftp://localhost:21/mockito/reset/when!",
ftpService.fetchFile())
}
@Test
fun `test fetch file folder - reset & doReturn`() {
Mockito.reset(urlProperties)
doReturn(
UrlProperties.FtpProperties(
url = "ftp://localhost:21/reset/doReturn"
)).`when`(urlProperties).ftp
assertEquals("I'm fetching a file from ftp://localhost:21/reset/doReturn!",
ftpService.fetchFile())
}
}
Run Code Online (Sandbox Code Playgroud)
示例@Service | `src/main/kotlin/com/example/service/FtpService.kt`
package com.example
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.runApplication
@SpringBootApplication
@EnableConfigurationProperties
@ConfigurationPropertiesScan
class MyApp
fun main(args: Array<String>) {
runApplication<MyApp>(*args)
}
Run Code Online (Sandbox Code Playgroud)
@ConfigurationProperties 和 @ConstructorBinding - `src/main/kotlin/com/example/service/UrlProperties.kt`
package com.example.service
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.ConstructorBinding
@ConstructorBinding
@ConfigurationProperties("url")
data class UrlProperties(val ftp: FtpProperties) {
data class FtpProperties(
val url: String,
)
}
Run Code Online (Sandbox Code Playgroud)
您可以使用注释提供自定义配置@TestPropertySource并提供不同的配置。通过在注释中提供属性可以实现类似的结果@SpringBootTest。那么您将不会创建自定义 URL 属性 bean。
另请参阅: https: //www.baeldung.com/spring-tests-override-properties
这种方法可能会很昂贵,因为您的 Spring 上下文将为具有此注释的每个测试类以及以下测试重新构建,这可能会为您的整体测试运行时间增加几秒。
您可以使用@MockkBean(来自https://github.com/Ninja-Squad/springmockk)仅模拟您的配置 bean 并指定返回值。这与您的解决方法不同,因为您仍然拥有完整的 Spring 上下文和只有一个模拟。
例子:
@TestConfiguration
@SpringBootTest(classes = [FtpService::class])
@AutoConfigureWebClient(registerRestTemplate = true)
class FtpServiceTest {
@Autowired
private lateinit var ftpService: FtpService
@MockkBean
private lateinit var urlProperties: UrlProperties
@Nested
inner class `scenario 1` {
@BeforeEach
fun setup() {
every { urlProperties.ftp } returns "url for scenario 1"
}
}
@Nested
inner class `scenario 2` {
@BeforeEach
fun setup() {
every { urlProperties.ftp } returns "url for scenario 2"
}
}
}
Run Code Online (Sandbox Code Playgroud)
在 Spring 中使用模拟也会对性能产生影响,而这个影响会比选项 1 中的小。
由于您只是测试 FTP 服务本身,因此您也可以在 Spring 测试中再次实例化。因此,对于您的测试,您创建一个使用所有 bean + 您的自定义属性的专用实例。
例子:
@TestConfiguration
@SpringBootTest(classes = [FtpService::class])
@AutoConfigureWebClient(registerRestTemplate = true)
class FtpServiceTest {
// example for a bean from the context you want to inject but not define yourself
@Autowired
private lateinit var otherBean: OtherBean
@Nested
inner class `scenario 1` {
private val urlProperties = UrlProperties(ftp = "url for scenario 1")
private val ftpService = FtpService(urlProperties, otherBean)
}
@Nested
inner class `scenario 2` {
private val urlProperties = UrlProperties(ftp = "url for scenario 2")
private val ftpService = FtpService(urlProperties, otherBean)
}
}
Run Code Online (Sandbox Code Playgroud)
这将是最快的方法 - 与 Spring 测试切片相结合,您可能会加快速度。但它不会测试实际的有线服务。
我不确定这对于您的用例是否可行:您还可以模拟您的 FTP 服务器(有一些可用的现成服务器)并在每次测试运行期间提供不同的响应。这将允许您测试由 spring 构建的 bean,并且您可能能够绕过 spring 上下文重建,从而进行相当快的测试。
| 归档时间: |
|
| 查看次数: |
7037 次 |
| 最近记录: |