如何使用 SpringBoot2、JUnit5 和 Kotlin 将配置属性注入单元测试

     2023-02-27     188

关键词:

【中文标题】如何使用 SpringBoot2、JUnit5 和 Kotlin 将配置属性注入单元测试【英文标题】:How can I inject config properties into a unit test, using SpringBoot2, JUnit5, and Kotlin 【发布时间】:2018-12-22 02:03:35 【问题描述】:

我的场景:

我正在构建一个使用 Kotlin 和 SpringBoot 2.0.3 的应用程序。我正在尝试在 JUnit5 中编写所有单元测试。这三个对我来说都是新的,所以我有点挣扎。

我正在使用 @ConfigurationProperties 类(而不是 @Value)将值从我的 application.yml 注入到我的 Spring 上下文中。

@Configuration
@ConfigurationProperties(prefix = "amazon.aws.s3")
class AmazonS3Config 
    val s3Enabled: Boolean = false
    val region: String = ""
    val accessKeyId: String = ""
    val secretAccessKey: String = ""
    val bucketName: String = ""

然后我有一个利用这些属性的 Kotlin 类,遵循 Kotlin/Spring 最佳实践将注入的类定义为构造函数参数。

class VqsS3FileReader(val amazonS3Config: AmazonS3Config) : VqsFileReader 
    companion object: mu.KLogging()

    override fun getInputStream(filePath: String): InputStream 
        val region: String = amazonS3Config.region
        val accessKeyId: String = amazonS3Config.accessKeyId
        val secretAccessKey: String = amazonS3Config.secretAccessKey
        val bucketName: String = amazonS3Config.bucketName
        logger.debug  "The configured s3Enabled is: $s3Enabled" 
        logger.debug  "The configured region is: $region" 
        logger.debug  "The configured accessKeyId is: $accessKeyId" 
        logger.debug  "The configured secretAccessKey is: $secretAccessKey" 
        logger.debug  "The configured bucketName is: $bucketName" 
        val file: File? = File(filePath)
        //This method is not yet implemented, just read a file from local disk for now
        return file?.inputStream() ?: throw FileNotFoundException("File at $filePath is null")
    

我还没有完成这个实现,因为我正试图让单元测试首先工作。所以目前,这个方法实际上并没有到达 S3,只是流式传输一个本地文件。

我的单元测试是我卡住的地方。我不知道如何将 application.yml 中的属性注入测试上下文。由于 ConfigProperty 类是作为构造参数传递的,所以我在单元测试中建立服务时必须传递它。我尝试了各种不起作用的解决方案。我发现这条信息很有帮助:

如果正在使用 Spring Boot,则可以使用 @ConfigurationProperties 而不是 @Value 注释,但目前这仅适用于 lateinit 或可为空的 var 属性(建议使用前者),因为尚不支持由构造函数初始化的不可变类。

所以这意味着我不能使用 class VqsS3FileReaderTest(amazonS3Config: AmazonS3Config): TestBase() ... 然后将配置传递给我的服务。

这是我目前拥有的:

@ActiveProfiles("test")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [AmazonS3Config::class, VqsS3FileReader::class])
class VqsS3FileReaderTest(): TestBase() 

    @Autowired
    private lateinit var amazonS3Config: AmazonS3Config

    @Autowired
    private lateinit var fileReader: VqsS3FileReader

    val filePath: String = "/fileio/sampleLocalFile.txt"

    @Test
    fun `can get input stream from a valid file path` () 
        fileReader = VqsS3FileReader(amazonS3Config)

        val sampleLocalFile: File? = getFile(filePath) //getFile is defined in the TestBase class, it just gets a file in my "resources" dir
        if (sampleLocalFile != null) 
            val inStream: InputStream = fileReader.getInputStream(sampleLocalFile.absolutePath)

            val content: String = inStream.readBytes().toString(Charset.defaultCharset())

            assert.that(content, startsWith("Lorem Ipsum"))
         else 
            fail  "The file at $filePath was not found." 
        
    

有了这个,我的测试运行,我的上下文似乎设置正确,但我的 application.yml 的属性没有被注入。对于我的调试输出,我看到以下内容:

08:46:43.111 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured s3Enabled is: false
08:46:43.111 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured region is: 
08:46:43.112 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured accessKeyId is: 
08:46:43.112 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured secretAccessKey is: 
08:46:43.112 [main] DEBUG com.ilmn.vqs.fileio.VqsS3FileReader - The configured bucketName is: 

所有空字符串,这是默认值。不是我在 application.yml 中的值:

amazon.aws.s3:
    s3Enabled: true
    region: us-west-2
    accessKeyId: unknown-at-this-time
    secretAccessKey: unknown-at-this-time
    bucketName: test-bucket

【问题讨论】:

【参考方案1】:

我在以下行中看到了错误:

@ContextConfiguration(classes = [AmazonS3Config::class, VqsS3FileReader::class])

请在此处放置 配置 类(而不仅仅是 bean)。

短 - 热修复测试

在主模块中创建类(如果缺少),如 VqsS3Configration(例如,在您有生产代码的模块中)

在与您的测试相同的包中创建类似 VqsS3TestConfigration 的类。此文件的内容:

@org.springframework.context.annotation.Configuration // mark, that this is configuration class
@org.springframework.context.annotation.Import(VqsS3Configration::class) // it references production configuration from test configuration
@org.springframework.context.annotation.ComponentScan // ask Spring to autoload all files from the package with VqsS3TestConfigration and all child packages
class VqsS3TestConfigration 
   /*put test-related beans here in future*/

然后去测试和更改声明:

@ContextConfiguration(classes = [VqsS3TestConfigration ::class]) // we ask Spring to load configuration here

我在这里创建了示例应用程序:https://github.com/imanushin/spring-boot2-junit5-and-kotlin-integration

请在 src 文件夹中执行 .\gradlew.bat testgradlew.bat bootRun 行。测试将检查我们是否能够读取属性。 bootRun 将打印自动加载的属性

无聊理论

首先 - Spring 有配置类 - 它们是加载和初始化其他类所必需的。 Configuration 类的主要目的不是 Service 或 Comonent 类 - 只是创建服务、组件等。

如果我们将 Spring 应用程序加载的算法简化,那么它将是这样的:

    查找配置类 阅读它们的注释,了解应该加载的类列表(例如参考树)(以及如何加载它们) 用不同的方式加载类:

3.1。对于使用 @ConfigurationProperties 注释的类 - 将配置项放在这里

3.2。对于使用 @RestController 注释的类 - 将它们注册为休息控制器

3.N.等等……

Spring怎么理解,应该加载什么配置?

    形式上是由 Spring Boot 完成的,但我将其命名为 Spring 了解几个初始配置 - 它们可以放入类 SpringApplicationBuilder、测试注解(见上文)、XML 上下文等。对于我们的案例,我们使用测试注解和 @ContextConfiguration 属性 递归获取所有导入的配置(例如,Spring 读取 @Import 注释,然后获取子项,然后检查它们的导入等) 使用Spring Factories从jar中自动获取配置

因此,在我们的例子中,Spring 将执行如下操作:

    从测试注释中获取配置 以递归方式获取所有其他配置 将所有类加载到比赛中 开始测试

【讨论】:

如果你需要,我可以用示例创建 github repo Manushin 谢谢...我想我理解你所说的大部分内容,但我做了更改,现在测试试图在不遵守我指定的弹簧配置文件的情况下启动我的上下文. (换句话说,它使用我的默认配置文件而不是我的测试配置文件,因此尝试运行 Flyway 并初始化我的数据库) @Nephthys76,这很奇怪......我把示例应用程序放在这里 - github.com/imanushin/spring-boot2-junit5-and-kotlin-integration 。我试图最小化它,所以它只展示应用程序属性 你在 GitHub 上的代码对我有帮助,谢谢!作为其他人的注意事项,我的执行抱怨重复配置。所以,我刚刚删除了我的 ***QuestionTestConfiguration 模拟类。【参考方案2】:

好的,我花了一整天的时间,但我终于将我的应用程序属性加载到我的单元测试上下文中。我做了 2 处更改:

首先,我将 @Service 注释添加到我的 VqsS3FileReader 服务中 - 我最初忘记了它。此外,虽然我更新了我的测试以不通过构造函数注入 AmazonS3Config,但我忽略了更新我的服务来做同样的事情。所以我改变了

这个:

class VqsS3FileReader(val amazonS3Config: AmazonS3Config) : VqsFileReader 
    companion object: mu.KLogging()
...

到这里:

@Service
class VqsS3FileReader : VqsFileReader 
    companion object: mu.KLogging()

    @Resource
    private lateinit var amazonS3Config: AmazonS3Config
...

最后,我在测试中修改了 Spring 注释。

从此:

@ActiveProfiles("test")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [AmazonS3Config::class, VqsS3FileReader::class])
class VqsS3FileReaderTest(): TestBase() 
...

到这里:

@ActiveProfiles("test")
@SpringBootTest
@ComponentScan("com.ilmn.*")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(SpringExtension::class)
@EnableAutoConfiguration
@SpringJUnitConfig(SpringBootContextLoader::class)
class VqsS3FileReaderTest(): TestBase() 
...

现在我的测试中似乎有非常多的注释......所以我将仔细研究它们每个人的实际作用,看看我是否可以减少它。但至少我的属性现在被注入到我的测试上下文中。

【讨论】:

如何使用 junit5 和 testcontainers 测试存储库?

...个示例项目,我在其中试验不同的技术。我有以下设置:SpringBoot2.3.4.RELEASE飞路7.0.1测试容器1.15.0-rc2Junit5.7.0如何使用testcontainer-ju 查看详情

springboot2---单元测试(junit5)(代码片段)

...套测试6、参数化测试Junit4到Junit5的迁移指南JUnit5的变化SpringBoot2.2.0版本开始引入JUnit5作为单元测试默认库注意࿱ 查看详情

springboot2单元测试和指标监控(代码片段)

单元测试1、JUnit5的变化SpringBoot2.2.0版本开始引入JUnit5作为单元测试默认库作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。JUnit5=JUnitPlatform+JUnitJupiter+JUnitVi... 查看详情

如何在 JUnit 5 中使用 @RestTemplateClient?

...Unit5?【发布时间】:2019-12-2314:51:10【问题描述】:我使用springboot2.1.7.RELEASE和junit5。不幸的是,@RestClientTest有问题,因为我收到java.lang.IllegalStateException:Unabletouseauto 查看详情

springboot2——数据访问的集成&单元测试(junit5)(代码片段)

SpringBoot2——数据访问的集成&单元测试(JUnit5)一、数据访问1.1数据库场景的自动配置(HikariDataSource)1.2整合druid数据源1.2.1自定义druid数据源1.2.2使用官方starter方式1.3整合MyBatis操作(重点)1.3.1整合过... 查看详情

springboot2核心功能---单元测试(代码片段)

一、JUnit5的变化SpringBoot2.2.0版本开始引入JUnit5作为单元测试默认库作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。JUnit5=JUnitPlatform+JUnitJupiter+JUnitVintageJUnit... 查看详情

springboot2_单元测试(代码片段)

07、单元测试1、JUnit5的变化SpringBoot2.2.0版本开始引入JUnit5作为单元测试默认库作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。JUnit5=JUnitPlatform+JUnitJupiter+JU... 查看详情

如何在 JUnit5 中使用 Mockito

】如何在JUnit5中使用Mockito【英文标题】:HowtouseMockitowithJUnit5【发布时间】:2017-04-1901:36:39【问题描述】:如何在Mockito和JUnit5中使用注入?在JUnit4中,我可以只使用@RunWith(MockitoJUnitRunner.class)注释。在JUnit5中是没有@RunWith注解?【... 查看详情

SpringBoot2 + Webflux - WebTestClient 总是返回“401 UNAUTHORIZED”

】SpringBoot2+Webflux-WebTestClient总是返回“401UNAUTHORIZED”【英文标题】:SpringBoot2+Webflux-WebTestClientalwaysreturns“401UNAUTHORIZED”【发布时间】:2020-02-0909:10:02【问题描述】:我正在尝试在Springboot2.1.8和Junit5下使用WebTestClient编写一些测试... 查看详情

Spring Boot 2 + Junit 5:带有@Value 的空值

】SpringBoot2+Junit5:带有@Value的空值【英文标题】:SpringBoot2+Junit5:nullwith@Value【发布时间】:2019-03-2522:26:57【问题描述】:我有一个带有SpringBoot2和Junit5的应用程序,现在我正在尝试进行测试。我有一个名为OrderService的类,它看起... 查看详情

springboot中的测试(test)

SpringBoot2.2之后用的Junit5,所以在这里使用的Junit5。SpringBoot会默认帮我们导入包,所以不用添加依赖了。注解:@BeforeAll:只执行一次,执行时机是在所有测试和@BeforeEach注解方法之前。@BeforeEach:在每个测试执行之前执行。@AfterEach:... 查看详情

ideaspringboot1.xjunit单元测试

...单元测试框架是junit,其中springboot1.x系列主要使用junit4,springboot2.x主要使用junit5;mock类和打桩的主要框架是mockito,主要有1.x(springboot1.x依赖),2.x(springboot2.0,2.1依赖),3.x(springboot2.2依赖)三个版本。0、关于单元测试首先... 查看详情

java应用xviii在java中使用junit5进行单元测试和自动化测试

...发效率。JUnit5是Java中流行的单元测试框架,本文将介绍如何在Java中使用JUnit5进行单元测试和自动化测试。二、单元测试2.1单元测试的基本概念和原理单元测试是一种测试方法,用于对软件系统中的最小可测试单元进行测试。这... 查看详情

正确配置 Spring Boot 2 和 JUnit 5

】正确配置SpringBoot2和JUnit5【英文标题】:ProperconfigurationofSpringBoot2andJUnit5【发布时间】:2018-08-0716:12:04【问题描述】:使用SpringBoot2.0.0.RC2。我写了一个配置类:@Configuration@ConditionalOnProperty("launchdarkly.sdkKey")publicclassLDClientConfigur 查看详情

如何使用 Gradle 和 JUnit 5 仅运行特定测试?

】如何使用Gradle和JUnit5仅运行特定测试?【英文标题】:HowtorunonlyspecifictestswithGradleandJUnit5?【发布时间】:2018-07-2008:41:15【问题描述】:使用Gradle及其对JUnit4的支持,我可以使用--tests选项选择特定测试,如下所示:$./gradlewtest--t... 查看详情

使用 Spring Boot 和 JUnit5 在 Intellij 中终止所有测试

】使用SpringBoot和JUnit5在Intellij中终止所有测试【英文标题】:AlltestsgetterminatedinIntellijwithSpringBootandJUnit5【发布时间】:2020-09-1921:45:29【问题描述】:我试图在我的SpringBoot应用程序中使用JUnit5编写一个简单的测试,但我注意到我... 查看详情

在 JUnit5 中使用 Testcontainers 和 gradle 时无法解析 'DockerImageName' 中的方法 'parse'

】在JUnit5中使用Testcontainers和gradle时无法解析\\\'DockerImageName\\\'中的方法\\\'parse\\\'【英文标题】:Cannotresolvemethod\'parse\'in\'DockerImageName\'whenusingTestcontainersandgradleinJUnit5在JUnit5中使用Testcontainers和gradle时无法解析\'DockerIma 查看详情

使用带有 JUnit5 扩展的测试容器

】使用带有JUnit5扩展的测试容器【英文标题】:UsingTestcontainerswithJUnit5extensions【发布时间】:2021-03-0701:39:56【问题描述】:我使用Springboot和JUnit5。我的应用适用于多个数据库:MySQL、Clickhouse等。为了集成测试,我创建了JUnit5扩... 查看详情