Spring 引导 - REST 控制器,使用 MockMvc 进行测试,环境属性

Spring Boot - REST controller, test with MockMvc, Environment properties

我在 Spring 引导应用程序中有一个 REST 控制器,简单地说:

@RestController
@RequestMapping("/api")
public class MyRestController {
    @Autowired
    private Environment env;

    private String property1;

    @PostConstruct
    private void init() {
        this.property1 = env.getProperty("myproperties.property_1");
    }

    @GetMapping("/mydata")
    public String getMyData() {     
        System.out.println("property1: " + this.property1);
        ...
    }

在 application.yml 中,我定义了 属性 类似于:

myproperties:
    property_1: value_1

当我使用 REST 控制器时,它按预期工作,读取了值 value_1,并且在 GET 方法中存在。

现在我想用单元测试来测试一下,也类似:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyApp.class)
public class MyRestControllerTest {
    @Autowired
    private MappingJackson2HttpMessageConverter jacksonMessageConverter;    

    @Autowired
    private PageableHandlerMethodArgumentResolver pageableArgumentResolver; 

    @Autowired
    private ExceptionTranslator exceptionTranslator;    

    private MockMvc restMyRestControllerMockMvc;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);

        final MyRestController myRestController = new MyRestController();

        this.restMyRestControllerMockMvc = MockMvcBuilders.standaloneSetup(myRestController)
                .setCustomArgumentResolvers(pageableArgumentResolver).setControllerAdvice(exceptionTranslator)
                .setConversionService(createFormattingConversionService()).setMessageConverters(jacksonMessageConverter)
                .build();
    }

    @Test
    public void getMyDataTest() throws Exception {
        restMyRestControllerMockMvc.perform(get("/api/mydata"))
            .andExpect(status().isOk());
    }

test中的方法执行时,属性属性1的值为null。

这是为什么?

以上代码部分由JHipster生成,我不确定这是否是最优方案,只是重复使用它。

谢谢!

MockMvcBuilders.standaloneSetup not loads SpringContext so properties data are not available. You can verify this by using @Value("${myproperties.property_1}") annotation directly inside MyRestControllerTest - it will return "value_1" value (but inside MyRestController - will return null).

请将其更改为MockMvcBuilders.webAppContextSetup并注入WebApplicationContext。 (最终你可以通过它的构造函数将 Environment bean 注入 MyRestController,但在我看来这是 Spring hacking。)

警告:还要记住(在 Maven 布局项目中)application.yml 需要复制到 src/test/resources。

代码示例:

@RestController
@RequestMapping("/api")
public class MyRestController {

    @Autowired
    private Environment env;

    private String envProperty;

    @Value("${myproperties.property_1}")
    private String valueProperty;

    @PostConstruct
    private void init() {
        this.envProperty = env.getProperty("myproperties.property_1");
    }

    @GetMapping("/mydata")
    public String getMyData() {
        System.out.println("envProperty: " + this.envProperty);
        System.out.println("valueProperty: " + this.valueProperty);
        return "";
    }

    @GetMapping("/myproblem")
    public String getMyProblem() {
        throw new IllegalArgumentException();
    }

}

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyApp.class)
public class MyRestControllerTest {

    private MockMvc restMyRestControllerMockMvc;

    @Autowired
    private WebApplicationContext context;

    @Before
    public void setup() {
        final MyRestController myRestController = new MyRestController();
//        this.restMyRestControllerMockMvc = MockMvcBuilders.standaloneSetup(myRestController)
//                .build();
        this.restMyRestControllerMockMvc = MockMvcBuilders.webAppContextSetup(context)
                .build();
    }

    @Test
    public void getMyDataTest() throws Exception {
        restMyRestControllerMockMvc.perform(get("/api/mydata"));
    }

    @Test
    public void getMyProblemTest() throws Exception {
        restMyRestControllerMockMvc.perform(get("/api/myproblem"))
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.status().isConflict());
    }

}

@ControllerAdvice
public class ControllerAdvicer {

    @ResponseStatus(HttpStatus.CONFLICT)
    @ExceptionHandler(IllegalArgumentException.class)
    public String assertionException(final IllegalArgumentException e) {
        return "xxx";
    }

}

使用 @Value 注释从您的 app.yml

中读取值
@RestController
@RequestMapping("/api")
public class MyRestController {
    @Autowired
    private Environment env;

    @Value("${myproperties.property_1}")
    private String property1;

    @GetMapping("/mydata")
    public String getMyData() {     
        System.out.println("property1: " + this.property1);
        ...
    }

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

我将 kasopey 答案标记为正确,因为它包含一个完整的答案,尽管其他回复者的部分答案也是正确的。

但我还是想知道这些线是干什么用的:

.setCustomArgumentResolvers(pageableArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setConversionService(createFormattingConversionService())
.setMessageConverters(jacksonMessageConverter)

因为你的解决方案要使用

MockMvcBuilders.webAppContextSetup(context)

这些方法不可用。如有必要,如何实现相同目标?

我的示例代码中缺少的方法如下所示:

... Create a FormattingConversionService which use ISO date format, instead of the localized one.
public static FormattingConversionService createFormattingConversionService() {
    DefaultFormattingConversionService dfcs = new DefaultFormattingConversionService ();
    DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
    registrar.setUseIsoFormat(true);
    registrar.registerFormatters(dfcs);
    return dfcs;
}

同样,大部分代码都是由 JHipster 生成的,这非常方便,但并不总是很清楚这是为什么以及这是为了什么。