在Spring Boot中进行WebAPI单元测试的方法时无需进行身份验证
■环境
环境
Spring Boot 1.2.5.Release
Spring Security 4.0.1.Release
Java 8
Maven 3.3.1
Spring Boot 1.2.5版本
Spring Security 4.0.1版本
Java 8
Maven 3.3.1版本
■简述
在使用Spring Boot进行认证时,在进行单元测试时需要进行一些措施以避免经过认证。下面将解释这些步骤。
■pom.xml 的意思是项目对象模型。
在本次的解释范围内,可能也有不需要的东西……
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- ■Spring Boot本体 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<!-- ■Spring Boot関連 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- ■テスト関連 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>4.0.1.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>1.2.3</version>
<scope>test</scope>
</dependency>
<!-- ■Javaライブラリ -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.4</version>
<scope>provided</scope>
</dependency>
<!-- ■フロントエンドフレームワーク -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
■ 具体的
首先,我们需要进行单元测试的类。
package com.sample.apps.controller;
import com.sample.SampleApplication;
import com.sample.common.test.controller.AbstractRestControllerTest;
import com.sample.common.test.data.AppsData;
import com.sample.apps.service.AppsService;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
/**
* AppsRestControllerのテストクラス
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleApplication.class)
@WebAppConfiguration
public class AppsRestControllerTest extends AbstractRestControllerTest {
@Mock
private AppsService appsService;
@InjectMocks
@Autowired
private AppsRestController appsRestController;
@Test
public void test_DataAppsDetail_OK() {
try {
long keyId = AppsData.getAppsData().getKeyId();
// サービス層のモック
Mockito.doReturn(AppsData.getAppsData()).when(appsService).findByKeyId(Mockito.anyLong());
// 認証済みにする
mvc.setAuthentication();
// WebAPIを実行
mvc.mockMvc.perform(MockMvcRequestBuilders
.get("/data/apps/detail/{keyId}", keyId)
)
// 認証されている
.andExpect(SecurityMockMvcResultMatchers.authenticated())
// HTTPステータスがOK(200)
.andExpect(MockMvcResultMatchers.status().isOk())
// contentTypeの評価
.andExpect(MockMvcResultMatchers
.content().contentType("application/json;charset=UTF-8"))
// content内容の評価(contentのサイズが0ではない)
.andExpect(MockMvcResultMatchers.jsonPath(
"$", CoreMatchers.not(Matchers.hasSize(0))))
// content内容の評価(仕様で定められたカラムが存在するか)
.andExpect(MockMvcResultMatchers
.jsonPath("$.keyId").exists())
.andExpect(MockMvcResultMatchers
.jsonPath("$.keyName").exists())
// request、responseを出力
.andDo(MockMvcResultHandlers.print());
Mockito.verify(appsService).findByKeyId(Mockito.anyLong());
} catch (Exception e) {
e.printStackTrace();
}
}
}
重点在于以下的部分。
// 認証済みにする
mvc.setAuthentication();
通過使其驗證,可以進行需要認證的Web API 單元測試。
接下來我們將進一步了解詳情。
首先,要進行RestController的測試,我們需要使用一個抽象類。
package com.sample.common.test.controller;
import com.sample.SampleApplication;
import com.sample.common.test.rules.MockMvcResource;
import org.junit.Rule;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* RestControllerのテストを行う抽象クラス
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleApplication.class)
@WebAppConfiguration
public abstract class AbstractRestControllerTest {
@Rule
@Autowired
public MockMvcResource mvc;
@Rule
public MockitoRule rule = MockitoJUnit.rule();
}
如果您想在控制器的字段中注入模拟对象,可以使用MockitoRule。
MockMvcResource是一个我们自己创建的类。
我们在测试类中定义了要共享使用的内容。
package com.sample.common.test.rules;
import javax.servlet.Filter;
import com.sample.common.test.data.LoginUserData;
import org.junit.rules.ExternalResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.stereotype.Component;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
/**
* テストクラス共通で使用する内容を定義する
*/
@Component
public class MockMvcResource extends ExternalResource {
@Autowired
private WebApplicationContext webAppContext;
private SecurityContext securityContext;
@Autowired
private Filter springSecurityFilterChain;
public MockMvc mockMvc;
@Override
protected void before() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
@Override
protected void after() {
SecurityContextHolder.clearContext();
}
/**
* 認証済みにさせる
*/
public void setAuthentication(){
Authentication authentication =
new UsernamePasswordAuthenticationToken(
LoginUserData.getLoginUserData(),
LoginUserData.getLoginUserData().getPassword(),
AuthorityUtils.createAuthorityList("ROLE_USER"));
securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
SecurityContextHolder.setContext(securityContext);
}
}
以下是经过验证的部分。
/**
* 認証済みにさせる
*/
public void setAuthentication(){
Authentication authentication =
new UsernamePasswordAuthenticationToken(
LoginUserData.getLoginUserData(),
LoginUserData.getLoginUserData().getPassword(),
AuthorityUtils.createAuthorityList("ROLE_USER"));
securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
SecurityContextHolder.setContext(securityContext);
}
在传递给UsernamePasswordAuthenticationToken类的用户信息上进行认证,并将其作为参数传入。
securityContext.setAuthentication(authentication);
SecurityContextHolder.setContext(securityContext);
将其设置为SecurityContextHolder可以获得认证状态。
这里提到的UsernamePasswordAuthenticationToken和SecurityContextHolder都是Spring Security的类。
通过使用Spring Security的类,我们可以实现认证状态。
另外,在这个时候,可以提供任何用户信息。
为了方便在测试类之间共享用户信息,我们将其放在了另一个类中。
package com.sample.common.test.data;
import com.sample.db.domain.entity.loginuser.custom.MLoginUser;
import com.sample.login.service.data.LoginUser;
/**
* テストクラスで使用する認証ユーザーの情報
*/
public class LoginUserData {
private static final MLoginUser mLoginUser = new MLoginUser(
"user1", // String loginUserId;
"user1" // String password;
);
private static final LoginUser loginUser = new LoginUser(mLoginUser);
public static LoginUser getLoginUserData(){
return loginUser;
}
}
在before和after方法中定义了用于共享的测试类的前处理和后处理。
@Override
protected void before() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
@Override
protected void after() {
SecurityContextHolder.clearContext();
}
■总结
只需一个选项,以下是对原文的汉语本地化:
・通过进行认证使得它可以进行需要认证的WebAPI的单元测试
・使用Spring Security的类可以使其认证通过
・认证信息可以随意填写
■参考资料
– 请提供相关资料
– 请提供参考书目
– 请提供相关参考资料
从0到Spring Security 4.0
使用Spring Boot进行控制器单元测试。