在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进行控制器单元测试。

广告
将在 10 秒后关闭
bannerAds