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 生成的,这非常方便,但并不总是很清楚这是为什么以及这是为了什么。
我在 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 生成的,这非常方便,但并不总是很清楚这是为什么以及这是为了什么。