单元测试——控制层单元测试

主要依赖:

<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>

转载于:https://my.oschina.net/kevin2kelly/blog/1531464

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值