主要依赖:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>2.5.0</version>
</dependency>
在spring-boot项目中只需要如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
主要代码:
import com.demo.app.DemoApplication; import com.demo.common.http.OperationCode; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.BDDMockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.containsString; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Author: liyang * Date: 03/09/2017 9:48 AM * Version: 1.0 * Desc: 对普通RESTful服务进行基本的测试。 */ @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = DemoApplication.class) @DirtiesContext public class DemoControllerTest { @Autowired private WebApplicationContext context; @MockBean private DemoService demoService; private MockMvc mockMvc; // 配置认证信息,以绕过 token 校验 /* @Autowired * private OAuthHelper oAuthHelper; * private String clientId; * private String[] roles; * private String[] scopes; * private String[] resourceIds; */ @Before public void setupMockMvc() { // 如果需要认证 /* clientId = "TMISWeb"; * roles = new String[]{"read", "write", "admin", "admin_authority"}; * scopes = new String[]{"read", "write", "admin"}; * resourceIds = null; */ mockMvc = MockMvcBuilders .webAppContextSetup(context) //.apply(springSecurity()) // 使用 spring security 进行认证验证 .build(); } @Test public void findFirst() throws Exception { //mock datas Demo demo = new Demo(); demo.setId(1); demo.setAge(0); //mock service level method invoke BDDMockito.when(demoService.findFirst()).thenReturn(demo); //assertions mockMvc.perform(get("/demo/first") .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON_UTF8) // .with(oAuthHelper.bearerToken(clientId, scopes, roles, resourceIds)) //携带认证信息 ) .andDo(print()) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().string(containsString("1"))) .andExpect(content().json("{\"operationCode\":1,\"body\":[{\"id\":1,\"age\":0}")) .andExpect(jsonPath("$", not(nullValue()))) .andExpect(jsonPath("$.operationCode", is(OperationCode.SUCCESS.getValue()))) .andExpect(jsonPath("$.operationCode").value(1)) .andExpect(jsonPath("$.body").isNotEmpty()); } }
很重要:
1. 对于post、put方法,被测试方法中有@RequestBody修饰的参数,需要添加 .contentType(MediaType.APPLICATION_JSON)后添加 .content(JSON.toJSONString(object))代码传递该类参数;
2. 对于get、delete方法,如果请求api的url中携带参数,如:url/demo-url/{demoId},那么请求是可以有两种写法:
1>. get("url/demo-url/{demoId}",demoId);
2>. get("url/demo-url/demoId-value"),这个demoId-value是demoId参数的真实值。
3. 在模拟方法调用时,如果调用处和被调用处传递的对象不能一致(即,需要传递的是同一个对象,但是真正调用时,可能因为反序列化等因素,得到的肯定不是原有对象(即使个属性值一样)),那么我们在模拟调用时,可以使用类似如下代码中updateDemo方法的入参代码块替代传递的对象(4种方式):
BDDMockito.when(demoService.updateDemo(org.mockito.Matchers.isA(DemoObject.class))).thenReturn(result); BDDMockito.when(demoService.updateDemo(org.mockito.Matchers.any(DemoObject.class))).thenReturn(result); BDDMockito.when(demoService.updateDemo(org.mockito.Matchers.any())).thenReturn(result); BDDMockito.when(demoService.updateDemo(org.mockito.Matchers.anyObject())).thenReturn(result);
4. 在模拟方法调用时,如果调用处使用matchers的isA、any(Type.class)、any和anyObject方法充当参数时,如果有多个参数,必须所有的参数都通过matchers的方式提供,否则会报异常:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
m matchers expected, n recorded: ……
其中,m表示有m个参数需要通过matchers(参数匹配器)方式提供,n表示只有n个参数通过该方式提供,还有m-n个也必须通过该方式提供。当然被mock方法,参数可以全为非matcher提供或全为matchers方式提供。比如:
错误:when(demo.updateDemo(null,any(Type.class))).thenReturn(result)
正确:when(demo.updateDemo(eq(null),any(Type.class))).thenReturn(result)
正确:when(demo.updateDemo(null,type)).thenReturn(result)
被测试方法:
@ApiOperation(value = "获得第一个demo", httpMethod = "GET", response = ResponseEntityBody.class) @GetMapping("/demo/first") public ResponseEntityBody findFirst() { ResponseEntityBody responseEntityBody = new ResponseEntityBody(); try { List<Demo> list = demoService.findFirst(); responseEntityBody.setBody(list); responseEntityBody.setOperationCode(OperationCode.SUCCESS); } catch (Exception e) { logger.error(e.getMessage()); } return responseEntityBody; }
OAuthHelper 是构造token的工具类,
oAuthHelper.bearerToken(clientId, scopes, roles, resourceIds) 用于构造认证信息,需要自己去实现。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; import org.springframework.stereotype.Component; import org.springframework.test.web.servlet.request.RequestPostProcessor; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; @Profile("test") @Component @EnableAuthorizationServer public class OAuthHelper extends AuthorizationServerConfigurerAdapter { // 定义常量 private static String USER_NAME = "name"; private static String USER_PASSWORD = "psw"; //账号可用 private static Boolean enable = true; //账号不过期 private static Boolean accountNonExpired = true; //凭证不过期 private static Boolean credentialsNonExpired = true; //账号不锁定 private static Boolean accountNonLocked = true; private static final String CLIENT_ID = "clientId"; @Autowired AuthorizationServerTokenServices tokenservice; @Autowired ClientDetailsService clientDetailsService; public RequestPostProcessor bearerToken(final String clientId, final String[] scopes, final String[] roles, final String[] resourceIds) { return mockRequest -> { OAuth2AccessToken token = createAccessToken(clientId, scopes, roles, resourceIds); mockRequest.addHeader("Authorization", "Bearer " + token.getValue()); return mockRequest; }; } private OAuth2AccessToken createAccessToken(final String clientId, final String[] nscopes, final String[] roles, final String[] nresourceIds) { ClientDetails client = clientDetailsService.loadClientByClientId(clientId); // 生成认证信息 Collection<GrantedAuthority> authorities = client.getAuthorities(); if (null != roles && roles.length > 0) { Arrays.stream(roles).forEach(r -> authorities.add(new SimpleGrantedAuthority(r))); } // 生成资源信息 Set<String> resourceIds = client.getResourceIds(); if (null != nresourceIds && nresourceIds.length > 0) { if (resourceIds != null) { Arrays.stream(nresourceIds).forEach(r -> resourceIds.add(r)); } } // 生成 scope Set<String> scopes = client.getScope(); if (null != nscopes && nscopes.length > 0) { if (null != scopes) { Arrays.stream(nscopes).forEach(s -> scopes.add(s)); } } // 生成 request Map<String, String> requestParameters = Collections.emptyMap(); boolean approved = true; String redirectUrl = null; Set<String> responseTypes = Collections.emptySet(); Map<String, Serializable> extensionProperties = Collections.emptyMap(); OAuth2Request oAuth2Request = new OAuth2Request(requestParameters, clientId, authorities, approved, scopes, resourceIds, redirectUrl, responseTypes, extensionProperties); // 获得用户信息 User userPrincipal = new User(USER_NAME, USER_PASSWORD, enable, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userPrincipal, null, authorities); OAuth2Authentication auth = new OAuth2Authentication(oAuth2Request, authenticationToken); return tokenservice.createAccessToken(auth); } @Override public void configure(final ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient(CLIENT_ID) .authorities("READ", "WRITE", "ADMIN") ; } }
需要添加oauth2及security的相关依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <version>4.2.3.RELEASE</version> <scope>test</scope> </dependency>