单元测试

mock 测试 controller

单元测试

json 与对象的转换

详情 请看 pan.day.learn.javase.jsons.JsonChangeObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

String oo = "{\n" +
" \"age\": 0,\n" +
" \"date\": \"2020-03-27T01:20:49.788Z\",\n" +
" \"dragonModelMap\": {},\n" +
" \"id\": \"string\",\n" +
" \"name\": \"string\",\n" +
" \"stringList\": [\n" +
" \"string\"\n" +
" ]\n" +
"}";


DragonModel dragonModel = JSONObject.parseObject(oo, DragonModel.class);

mock 学习

Mockito

verify

若没有按希望的执行则会抛异常!

Mockito.verify (mockBean ).someMethod();表示:someMethod方法调用了一次,相当于times(1)
Mockito.verify (mockBean, Mockito.times(n) ).someMethod();表示:someMethod方法调用了n次
Mockito.verify (mockBean, Mockito.never() ).someMethod();表示:someMethod方法未执行
Mockito.verify (mockBean, Mockito. atLeastOnce() ).someMethod();表示:someMethod方法至少执行过一次,相当于atLeast(1)

做测试桩

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import pan.day.jpa.entity.StudentEntity;
import pan.tools.enums.HumanEnum;

import java.util.Date;

/**
* 测试 restful 风格
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class RESTful {

@Autowired
private WebApplicationContext webApplicationContext;

private MockMvc mockMvc;

// 在每个测试方法执行之前都初始化MockMvc对象
@Before
public void setupMockMvc() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}

/**
* 尝试测试一个不存在的请求 /user/1
* 查询单条数据
*
* @DisplayName 自定义测试方法展示的名称
*/
@DisplayName("测试根据Id获取User")
@Test
public void askUnWrite() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
// 构造一个get请求
.get("/student/findOne/1")
.header("Origin", "*")
// 请求类型 json
.contentType(MediaType.APPLICATION_JSON)

)
// 期待返回的状态码是4XX,因为我们并没有写/user/{id}的get接口
// .andExpect(MockMvcResultMatchers.status().is4xxClientError());
// .andExpect(MockMvcResultMatchers.status().is2xxSuccessful())
// $.length() 将content 转成json,判断个数
.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(7))
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("pan"))
// .andDo(MockMvcResultHandlers.print())
// 返回结果集
.andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
// 单个断言 过不了的话会出 AssertionError
Assert.assertEquals(200, mvcResult.getResponse().getStatus());
// 这里应该是一个application/json
String contentType = response.getContentType();
// 转成json对象
JSONObject jsonObject = JSONObject.parseObject(response.getContentAsString());
System.out.println(contentType);
Assert.assertThat(jsonObject.get("name"), Matchers.is("pan"));
// 转成具体对象
StudentEntity studentEntity = JSONObject.parseObject(response.getContentAsString(), StudentEntity.class);
System.out.println(studentEntity);
}

/**
* post请求
*/
@Test
public void postMock() throws Exception {

StudentEntity studentEntity = new StudentEntity();
studentEntity.setName("wy")
.setAge(22)
.setStatus(HumanEnum.OK)
.setCreateTime(new Date())
.setCourseNumber("classOne");

String jsonString = JSON.toJSONString(studentEntity);
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
// MockMvcRequestBuilders.post("/url") : 构造一个post请求
.post("/student/save")
// 可使用此处添加token信息
.header("Origin", "*")
.accept(MediaType.APPLICATION_JSON)
// 传参,因为后端是@RequestBody所以这里直接传json字符串
.content(jsonString)
.contentType(MediaType.APPLICATION_JSON)
// 期望
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
}
}

SpringBoot 测试Controller

MockHttpServlet

实现了 HttpServletRequest HttpServletResponse
可以设置请求头,一些属性等,可以模拟接收 HttpServletRequest 来获取一些信息。
但是并不能发起一个请求,测试controller

1
2
3
4
5
6
7
8
9
10
11
12
private MockHttpServletRequest mockHttpServletRequest;

@Before
public void init() {
mockHttpServletRequest = new MockHttpServletRequest();
mockHttpServletRequest.setCharacterEncoding("UTF-8");
mockHttpServletResponse = new MockHttpServletResponse();
mockHttpServletRequest.addHeader("Authorization", "65734685ef5e4294e87340648e039f91_7u931ed01a2611ea7050c7840ac94150");

// mockHttpServletRequest 就相当于一个HttpServletRequest了
}

集成Web环境方式 Controller

MockMvcBuilders.webAppContextSetup(WebApplicationContext context):将会从该上下文获取相应的控制器并得到相应的MockMvc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

@Autowired
private WebApplicationContext webApplicationContext;

private MockMvc mockMvc;

@Test
/**
* mock 测试
*
* DefaultMockMvcBuilder有一个build方法,该方法产生MockMvc
*/
public void testMock(){
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
// DefaultMockMvcBuilder defaultMockMvcBuilder = MockMvcBuilders.webAppContextSetup(webApplicationContext);
}

独立测试方法

MockMvcBuilders.standaloneSetup(Object… controllers);通过参数指定一组控制器,这样就不需要从上下文获取了;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

private MockMvc mockMvc;

@Test
/**
* 独立方法
* StandaloneMockMvcBuilder也有一个 build() 方法
*
*/
public void standMock() {

mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();

}

使用 mockMvc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

// perform 方法
( get("/users") 或 post("/add") )
get("/users").accept(MediaType.APPLICATION_JSON_UTF8)

post("/add").contentType(MediaType.APPLICATION_JSON).content(JSON.toJSONString(user)

// 例子

mockMvc.perform(get("/users").accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andDo(print())
.andExpect(content().string(equalTo("[]")));


mockMvc.perform(post("/add").contentType(MediaType.APPLICATION_JSON).content(JSON.toJSONString(user)))
.andExpect(status().isOk())
.andDo(print())
.andReturn().getResponse().getContentAsString();

问题

在controller 层发现 没有注入service。当使用standaloneSetup时。

解决

使用 web 集成方式 测试controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();

但是这样会报 java.lang.IllegalArgumentException: Header value must not be null

参考:https://stackoverflow.com/questions/46405193/spring-using-mockmvc-test-with-cors-filter

所以还得加 .header("Origin","*")

ConsultationVo consultationVo = new ConsultationVo();
consultationVo.setOperatorId("123456789");

mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();

// mockMvc = MockMvcBuilders.standaloneSetup(new ConsultationController()).build();
try {
mockMvc.perform(
MockMvcRequestBuilders.post("/consultations/getAll")
.header("Origin","*")
.accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
// .param("name","hi")
.content(JSON.toJSONString(consultationVo))
.contentType(MediaType.APPLICATION_JSON)

)
// .andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
;
} catch (Exception e) {
e.printStackTrace();
}


结果会出现在打印中的 body 里

参考

不大理解
https://zhuanlan.zhihu.com/p/61342833

打印结果 会清晰一些
https://zhuanlan.zhihu.com/p/98074553

MockMvc

文档:https://docs.spring.io/spring/docs/5.2.2.RELEASE/spring-framework-reference/testing.html#testing-introduction

MockMvcBuilder 来产生 MockMvc :有以下两种方式
StandaloneMockMvcBuilder和DefaultMockMvcBuilder,分别对应两种测试方式,即独立安装和集成Web环境测试(此种方式并不会集成真正的web环境,而是通过相应的Mock API进行模拟测试,无须启动服务器)。

一把来说:用静态工厂MockMvcBuilders 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

public class MockMvcBuilders {

/**
* Build a {@link MockMvc} instance using the given, fully initialized
* (i.e., <em>refreshed</em>) {@link WebApplicationContext}.
* <p>The {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
* will use the context to discover Spring MVC infrastructure and application
* controllers in it. The context must have been configured with a
* {@link javax.servlet.ServletContext ServletContext}.
*/
public static DefaultMockMvcBuilder webAppContextSetup(WebApplicationContext context) {
return new DefaultMockMvcBuilder(context);
}

/**
* Build a {@link MockMvc} instance by registering one or more
* {@code @Controller} instances and configuring Spring MVC infrastructure
* programmatically.
*
* <p>This allows full control over the instantiation and initialization of
* controllers and their dependencies, similar to plain unit tests while
* also making it possible to test one controller at a time.
*
* <p>When this builder is used, the minimum infrastructure required by the
* {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
* to serve requests with annotated controllers is created automatically
* and can be customized, resulting in configuration that is equivalent to
* what MVC Java configuration provides except using builder-style methods.
*
* <p>If the Spring MVC configuration of an application is relatively
* straight-forward &mdash; for example, when using the MVC namespace in
* XML or MVC Java config &mdash; then using this builder might be a good
* option for testing a majority of controllers. In such cases, a much
* smaller number of tests can be used to focus on testing and verifying
* the actual Spring MVC configuration.
*
* @param controllers one or more {@code @Controller} instances to test
*/
public static StandaloneMockMvcBuilder standaloneSetup(Object... controllers) {
return new StandaloneMockMvcBuilder(controllers);
}

}

-------------本文结束感谢您的阅读-------------