springboot接口慢_[实习记录]7.14SpringBoot配置Controller层接口测试

在web开发中,我们通常进行Service层服务的单元测试。有时候,我们也需要在Controller层进行接口测试,它有如下好处:

规范化接口测试。如果不在Controller层测试,开发人员往往需要使用浏览器或者Postman等工具访问接口,流程繁琐。

提高开发效率。如果不在Controller层测试,开发人员需要启动整个工程判断代码正确性。配置Controller层测试后,无需启动所有接口;使用StandAlone方式测试时,甚至无需启动web工程。

配置监控服务。可以配置监控进程,对所有接口进行周期性检测,发现接口异常时进行通知。

Spring提供两种形式的集成测试:

独立测试方式(StandaloneMockMvcBuilder)。优点:运行快,无需启动web。缺点:对于依赖其他Component的接口,需要手动mock所依赖的Component,手动设置其返回收据。

集成测试方式(DefaultMockMvcBuilder)。优点:加载了ApplicationContext,所依赖的各种Component已经注入,完全模拟运行时场景,无需手动造数据。缺点:相比于独立测试方式,运行较慢。

1. 测试环境准备

测试工程使用的Spring版本如下:

SpringFramework: 4.3.8

SpringBoot:1.5.4 依赖配置如下:

org.springframework.boot

spring-boot-starter-test

1.5.4.RELEASE

test

你没有看错,就只需要一个spring-boot-starter-test。点进去你会发现,它包括了junit、spring-test、mockito等一些列依赖。

写一个Controller,用于测试: @Controller public class SampleController { @Autowired private UserService userService;

@RequestMapping("/")

@ResponseBody

public String home(){

return "Hello World";

}

@RequestMapping("/getUser/{id}")

@ResponseBody

public User getUser(@PathVariable String id){

return userService.queryUserById(id);

}

@RequestMapping("getJson")

public ResponseEntity getJson(@RequestParam(defaultValue = "null message") String message){

Map resultMap = new HashMap<>(4);

resultMap.put("code", 0);

resultMap.put("message", message);

return new ResponseEntity(resultMap, HttpStatus.OK);

}

}

有3个接口,分别返回静态字符串、查询数据库返回对象、返回静态json数据。其中用到UserService,只有一个查询的方法:

@Service

public class UserService{

@Autowired

private JdbcTemplate jdbcTemplate;

public User queryUserById(String id){

String sql = "select id,username from user where id=?";

RowMapper rowMapper = new BeanPropertyRowMapper(User.class);

User user = jdbcTemplate.queryForObject(sql, rowMapper, id);

return user;

}

}

下面要对这个Contoller中的方法写测试用例,如上文所述,分为独立测试和接口测试两种方式。

2. 独立测试方式

独立测试方式使用了mockito,代码如下所示:

@RunWith(MockitoJUnitRunner.class)

public class StandAloneSampleControllerTest{

private MockMvc mockMvc;

@InjectMocks

private SampleController sampleController;

@Mock

private UserService userService;

@Before

public void setUp() throws Exception{

this.mockMvc = MockMvcBuilders.standaloneSetup(sampleController).build();

MockitoAnnotations.initMocks(this);

}

@Test

public void home() throws Exception{

MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get("/"))

.andExpect(MockMvcResultMatchers.status().isOk())

.andExpect(MockMvcResultMatchers.content().string("Hello World"))

.andReturn();

System.out.println(mvcResult.getResponse().getContentAsString());

}

@Test

public void getJson() throws Exception{

MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get("/getJson" )

.param("message", "nothing to show")

.accept(MediaType.APPLICATION_JSON_UTF8))

.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8))

// jsonPath refer to https://github.com/json-path/JsonPath.

.andExpect(MockMvcResultMatchers.jsonPath(".message").value("nothing to show"))

.andReturn();

System.out.println(mvcResult.getResponse().getContentAsString());

}

/**

* standAlone方式,Service DAO都没有注入bean,不能进行和Service有关的测试.

*

*@throws Exception

*/

@Test

public void getUser() throws Exception{

given(this.userService.queryUserById("1"))

.willReturn(new User(1, "Mock User"));

MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get("/getUser/{id}",1))

.andExpect(MockMvcResultMatchers.status().isOk())

.andExpect(MockMvcResultMatchers.jsonPath(".id").value(1))

.andReturn();

System.out.println(mvcResult.getResponse().getContentAsString());

}

}

其中@before setUp()方法将mockMvc初始化为StandaloneMockMvc,然后使用mockMvc模拟请求。 如果不加 @InjectMocks private SampleController sampleController 和@Mock private UserService userService ,getUser()方法会测试失败,NPE异常。这是因为Standalone方式不会加载Bean,如果没有设置依赖的Component的返回结果,mockMvc默认返回null。所以,需要手动将Controller层中getUser()方法所依赖的UserService注入MockBean,然后手动设置数据。例如上面代码中的:

@Test

public void getUser() throws Exception{

given(this.userService.queryUserById("1")).willReturn(new User(1, "Mock User"));

// ...

这句话的意思是,在userService的queryUserById("1")方法返回new User(1, "Mock User")时进行测试。如此依赖,也测试成功了。此时,通过输出的数据可以看到,getUser()获取的User对象是"Mock User"。

注意代码中,getJson()方法内

.andExpect(MockMvcResultMatchers.jsonPath(".message").value("nothing to show"))

可以使用MockMvcResultMatchers中自带的jsonPath判断返回json中“message”于是否为"nothing to show"字符串。

3. 集成测试方式

基于spring-boot-starter-test的集成测试配置也非常简单,只需要使用@SpringBootTest注解配置程序启动的入口即可。这里我们实现了一个BaseContextControllerTest父类:

@RunWith(SpringJUnit4ClassRunner.class)

@WebAppConfiguration

@SpringBootTest(classes = AppStarter.class)

public class BaseContextControllerTest {

@Autowired

protected WebApplicationContext wac;

}

其中AppStarter是web的启动类。使用@Autowired配置WebApplicationContext。SampleController对应的测试类如下:

public class IntegrateSampleControllerTest extends BaseContextControllerTest{

private MockMvc mockMvc;

@Before

public void setup() throws Exception{

this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();

}

@Test

public void home() throws Exception{

MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get("/"))

.andExpect(MockMvcResultMatchers.status().isOk())

.andExpect(MockMvcResultMatchers.content().string("Hello World"))

.andReturn();

System.out.println(mvcResult.getResponse().getContentAsString());

}

@Test

public void getJson() throws Exception{

MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get("/getJson" )

.param("message", "nothing to show")

.accept(MediaType.APPLICATION_JSON_UTF8))

.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8))

// jsonPath refer to https://github.com/json-path/JsonPath.

.andExpect(MockMvcResultMatchers.jsonPath(".message").value("nothing to show"))

.andReturn();

System.out.println(mvcResult.getResponse().getContentAsString());

}

/**

* 集成测试方式,加载了application context,所依赖的service都会注入.

*

*@throws Exception

*/

@Test

public void getUser() throws Exception{

MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get("/getUser/{id}",1))

.andExpect(MockMvcResultMatchers.status().isOk())

.andExpect(MockMvcResultMatchers.jsonPath(".id").value(1))

.andReturn();

System.out.println(mvcResult.getResponse().getContentAsString());

}

}

注意,getUser()不再需要手动mock数据,而是使用了UserService真实返回的数据。另外,如果你的spring-boot版本比较低(比如1.1.9),可能不支持@SpringBootTest注解。这时可以使用@ContextConfiguration(classes = {BaseTestConfig.class})加载配置信息。BaseTestConfig的实现示例如下:

@Configuration

@ComponentScan({ "package name" })

@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})

public class BaseTestConfig{

@Bean

public static PropertySourcesPlaceholderConfigurer propertyConfigurer(){

return new PropertySourcesPlaceholderConfigurer();

}

}

4. 基于JsonSchema进行验证

通常,我们希望验证接口返回的json数据格式是否正确,这时程序正确的前提。json-schema-validator提供了json格式的验证方法(json schema可以参考RFC draft)。只需引入以下依赖:

com.github.java-json-tools

json-schema-validator

2.2.8

新建baseSchena.json资源文件,存放jsonSchema模板:

{

"$schema": "http://json-schema.org/draft-04/schema#",

"definitions": {},

"properties": {

"code": {

"id": "/properties/code",

"type": "integer"

},

"message": {

"id": "/properties/message",

"type": "string"

},

"result": {

"id": "/properties/result",

"type": "object"

}

}

},

"type": "object"

}

可以使用jsonSchema在线生成工具根据实际json数据,生成jsonSchema。然后在BaseContextControllerTest中写个方法用于测试:

/**

* 测试是否返回的Json是否符合Basic格式

* Basic格式:{"message":String,"result":Object,"code":Integer}

*

*@param testStr

*@return

*@throws Exception

*/

protected Boolean testBasicJsonSchema (String testStr) throws Exception{

final JsonNode testNode = JsonLoader.fromString(testStr);

final JsonNode basicSchemaPlain = JsonLoader.fromResource(BASIC_SCHEMA_PATH);

final JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.byDefault();

final JsonSchema basicSchema = jsonSchemaFactory.getJsonSchema(basicSchemaPlain);

ProcessingReport report = basicSchema.validate(testNode);

return report.isSuccess();

}

在getJson()方法中调用:

String responseStr = mvcResult.getResponse().getContentAsString();

System.out.println(responseStr);

Boolean success = super.testBasicJsonSchema(responseStr);

assertTrue(success);

显然是通不过的,因为没有“result”域。json-schema-validator的用法可以参考github。

5. 其他参考

本文来自网易实践者社区,经作者葛志诚授权发布。

你可能感兴趣的:(springboot接口慢)