在 Autowired 服务类中测试 REST API 时,如何使用测试 application.yml 覆盖主 application.yml?

Posted

技术标签:

【中文标题】在 Autowired 服务类中测试 REST API 时,如何使用测试 application.yml 覆盖主 application.yml?【英文标题】:How to override main application.yml with testing application.yml when testing REST API in an Autowired service class? 【发布时间】:2019-12-17 05:15:13 【问题描述】:

我正在使用 TestNG 为我的应用程序的 REST API 编写自动化测试。该应用程序有一个 RestController,其中包含一个 @Autowired 服务类。当使用 HTTP GET 请求调用 REST 端点时,服务会查找 XML 文件的存储目录,将其内容转换为对象并将它们存储在数据库中。对于我的问题,重要的是存储目录的路径存储在 /src/main/resources/application.yml (source.storage) 中,并通过 @Value 注释导入。

现在,我在 src/test/resources/application.yml 中也有 source.storage 属性,它指向 src/test 中的另一个目录,我在其中存储我的测试 XML 文件,并将它们导入到我的测试类中@Value 再次注解。我的测试使用 HTTP GET 调用 REST 端点。但是,该服务似乎仍将 source.storage 属性绘制为主 application.yml,而我希望该值被测试 application.yml 文件中的值覆盖。换句话说,该服务尝试从应用程序存储目录中导入 XML 文件,而不是从我的测试存储中。

@ActiveProfiles 和 @TestPropertySource 似乎对我不起作用。扫描主 application.yml 的存储属性不是一种选择,因为最终 application.yml 将从 Spring Cloud Config 中提取,我不知道主 application.yml 的位置。

有没有一种方法可以让@Autowired 服务从测试 application.yml 中提取 source.storage 属性,而不是从主要的?

任何建议将不胜感激。

谢谢,彼得

【问题讨论】:

【参考方案1】:

嗯,这实际上取决于您要构建的内容,是控制器的某种单元测试还是更可能是集成测试。这两种方法都在 tutorial 中进行了解释。

如果您尝试编写 集成测试,从您的问题来看,这似乎更有可能,那么 @ActiveProfiles@TestPropertySource 应该适合您。我建议使用配置文件,在具有很多属性的不断增长的应用程序中,只需替换一些属性进行测试会更方便一些。以下是在为控制器端点编写集成测试时对我有用的设置:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class AreaControllerTest 

    @Autowired
    TestRestTemplate rest;

    @MockBean
    private JobExecutor jobExecutor;


    @Test
    public void test01_List() 
        //
    

    @Test
    public void test02_Get() 
        //
    
    
    // ...

有几件重要的事情。

测试属性位于src/test/resources/application-test.properties 中,并与application.properties 中的属性合并,正如@ActiveProfiles("test") 注释所暗示的那样。 Essential 也是 @RunWith(SpringRunner.class),它是特定于 JUnit 的,有关 TestNG 替代方案,请参阅 this SO question。 最后,@SpringBootTest 注释将启动整个应用程序上下文。 @FixMethodOrder@DirtiesContext 是测试用例的进一步设置,并不是真正需要的。 还要注意@MockBean 注释,在这种情况下,我们不想使用JobExecutor 的实际实现,因此我们将其替换为模拟。

如果你想编写单元测试,你只想自己检查控制器和服务的逻辑,那么你必须有两个测试类,每个测试各自的类。测试服务应该是标准的单元测试,测试控制器有点棘手,可能更倾向于部分集成测试。如果这是您的情况,我建议使用上述教程中解释的MockMvc 方法。来自那里的小sn-p:

@RunWith(SpringRunner.class)
@WebMvcTest(GreetingController.class)
public class WebMockTest 

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private GreetingService service;

    @Test
    public void greetingShouldReturnMessageFromService() throws Exception 
        when(service.greet()).thenReturn("Hello Mock");
        this.mockMvc.perform(get("/greeting")).andDo(print()).andExpect(status().isOk())
                .andExpect(content().string(containsString("Hello Mock")));
    

注意 @MockBean 注释,它模拟服务,您可以在其中指定自己的模拟行为。这一点很关键,因为这种测试不会加载整个应用程序上下文,而只会加载 MVC 上下文,因此服务不可用。同样,在集成测试中,@RunWith(SpringRunner.class) 注释是必不可少的。最后@WebMvcTest(GreetingController.class) 只启动GreetingController 类的MVC 上下文,而不是整个应用程序。

【讨论】:

【参考方案2】:

您可以尝试将属性直接提供给 Spring Boot 测试。

    @SpringBootTest(properties= "source.storage=someValue")

关于获取错误属性源的应用程序,您还应该检查您的应用程序是否正在正确构建。

【讨论】:

以上是关于在 Autowired 服务类中测试 REST API 时,如何使用测试 application.yml 覆盖主 application.yml?的主要内容,如果未能解决你的问题,请参考以下文章

单元测试中的 Spring Boot @Autowired 返回 NullPointerException

NUnit 5 Spring MVC 测试 NoSuchBeanDefinitionException 用于子模块中的 Autowired 依赖项

如何为同一服务使用@Autowired 注解两个或多个不同的组件类?

Junit问题01 利用 @Autowired 注入失效问题

Spring - 如何正确使用@Autowired 来防止控制器/ MockMvc 为空?

@Autowired 在静态类中