如何使用MockMVC实现登录
在本系列的前一篇文章中,笔者介绍了如何配置和调整MeterSphere的开发测试环境,并完成了测试基类的编写。
首先来观察一下登录的接口。在LoginController中有如下接口,
代码语言:javascript复制 @PostMapping(value = "/signin")
public ResultHolder login(@RequestBody LoginRequest request) {
SecurityUtils.getSubject().getSession().setAttribute("authenticate", UserSource.LOCAL.name());
return userService.login(request);
}
可以了解到
- 这是一个post类型的接口,
- url为“/signin”
- 入参是一个LoginRequest的实例
先封装一下通过MockMvc发送post请求的方法
代码语言:javascript复制 public String doPost(String url,String content ) throws Exception {
return mockMvc.perform(post(url)
.content(content)
.contentType("application/json;charset=utf-8;")
)
.andReturn().getResponse().getContentAsString();
}
调用时,只需要提供url和content即可,简化了用例中的使用。 接下来看一下如何调用登录接口了。
代码语言:javascript复制 @Test
public void testLogin() throws Exception {
LoginRequest loginRequest= new LoginRequest();
loginRequest.setUsername("admin");
loginRequest.setPassword("metersphere");
doPost("/signin",JSON.toJSONString(loginRequest));
}
使用FastJson对LoginRequest的实例进行了序列化,然后调用doPost实现对登录接口的调用。 由于admin用户是系统自带的管理员用户,因此本次登录会成功。 在项目的resouce/db.migration目录下有V3__init_data.sql这一文件,指定了admin用户的初始化信息。
代码语言:javascript复制INSERT INTO user (id, name, email, password, status, create_time, update_time, language, last_workspace_id, last_organization_id, phone)
VALUES ('admin', 'Administrator', 'admin@metersphere.io', md5('metersphere'), '1', unix_timestamp() * 1000, unix_timestamp() * 1000, NULL, '', NULL,
NULL);
Session
登录几乎是所有系统级别测试用例的基础。在客户端前台如webmobile,如果不登录的话几乎就无法来操作系统进行测试。而在单元测试、集成测试时,可以绕过前台的限制,直接对后端服务发起调用, 为啥还需要登录呢? 首先还是权限的问题,
在客户端调用服务端接口时,一般都会进行鉴权,以确认客户端是否是合法用户。未经登录获取授权,直接调用后端服务会被系统拒绝。该系统使用了Shiro来管理鉴权,因此会有如下的报错信息
代码语言:javascript复制 Body = {"success":false,"message":"This subject is anonymous - it does not have any identifying principals and authorization operations require an identity to check against. A Subject instance will acquire these identifying principals automatically after a successful login is performed be executing org.apache.shiro.subject.Subject.login(AuthenticationToken) or when 'Remember Me' functionality is enabled by the SecurityManager. This exception can also occur when a previously logged-in Subject has logged out which makes it anonymous again. Because an identity is currently not known due to any of these conditions, authorization is denied.","data":null}
因此通过MockMvc进行登录时,可以将登录时获取到的session在后续请求中带上即可通过Shiro的鉴权了。 因此需要改造一下之前的doPost请求。
代码语言:javascript复制 public String doPost(String url,String content ) throws Exception {
return mockMvc.perform(post(url)
.content(content)
.contentType("application/json;charset=utf-8;")
.session(session)
)
.andDo(print()) //打印出请求和相应的内容
.andExpect(status().isOk()) //返回的状态是200
.andReturn().getResponse().getContentAsString();
}
同样的,在发送get请求时 ,也需要带上登录时获取的session。
代码语言:javascript复制 public String doGet(String url) throws Exception {
return mockMvc.perform(get(url)
.contentType("application/json;charset=utf-8;")
.session(session)
)
.andDo(print()) //打印出请求和相应的内容
.andExpect(status().isOk()) //返回的状态是200
.andReturn().getResponse().getContentAsString();
}
Shiro Role
来看一下“创建项目”这一典型的接口。
代码语言:javascript复制 @PostMapping("/add")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER,}, logical = Logical.OR)
public Project addProject(@RequestBody Project project) {
return projectService.addProject(project);
}
在这个接口上,团队使用了Shiro的@RequiresRoles来标注这个接口。从字面上来看,新增项目这个接口只能是被具备测试经理或者测试用户角色的用户来使用。而在同一个schema文件V3__init_data.sql中,对admin定义为测试经理了,也就是默认可以登录。
代码语言:javascript复制INSERT INTO user_role (id, user_id, role_id, source_id, create_time, update_time)
VALUES (uuid(), 'admin', 'test_manager', (SELECT id FROM workspace WHERE name LIKE '默认工作空间'), unix_timestamp() * 1000, unix_timestamp() * 1000);
如果集成测试时启动了整个容器上下文,这个Controller层的接口调用也是需要受到Shiro权限管理的。非此类角色的用户即使登录后调用也会报“没有权限”的错误。
Workspace工作空间
其次,从业务逻辑上看,也是需要登录的。在项目的Service层接口中,有不少如下的代码,
代码语言:javascript复制String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
由于Workspace工作空间是一个比测试项目、测试计划等业务对象更大的上下文,系统是根据工作空间来隔离不同用户所能看见的上述对象,例如在新创建测试计划时,需要关联该项目所在的工作空间。 addTestPlanRequest.setWorkspaceId(workspace.getId()); 而工作空间也只有在用户登录后才能查询和获取具体的id值。
完整登录方法
代码语言:javascript复制@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Slf4j
public class Project2TestCaseLongJourneyTest extends TestApp {
static MockHttpSession session= new MockHttpSession();
static Workspace workspace= new Workspace();
@Order(0)
@Test
@DisplayName("00登录")
public void loginSetup() throws Exception {
LoginRequest loginRequest= new LoginRequest();
loginRequest.setUsername("admin");
loginRequest.setPassword("metersphere");
doPost("/signin",JSON.toJSONString(loginRequest));
String result= doGet("/workspace/list/userworkspace/admin");
List<Workspace> workspaceList= JSON.parseArray(String.valueOf(JSON.parseObject(result,ResultHolder.class).getData()),Workspace.class);
assertThat(workspaceList).isNotEmpty();
workspace=workspaceList.get(0);
}
这个登录用例首先使用admin用户完成登录,然后在登录过程中通过MockMVC保存了session。在完成登录后,再次通过接口来获取该用户的工作空间,以供后续接口调用时使用。 这样,我们的MeterSphere之旅就可以开始了。