Mock Test with Spring Security

mock-test

環境設置

  • org.junit.jupiter:junit-jupiter:5.10.0:提供 JUnit 5 測試框架,用於編寫和運行測試。
  • org.mockito:mockito-junit-jupiter:5.5.0:將 Mockito 與 JUnit 5 集成,允許在測試中創建 mock 對象。
  • org.assertj:assertj-core:3.24.2:提供 AssertJ,一個流暢的斷言庫,用於編寫更具可讀性和可維護性的測試斷言。
  • org.springframework.security:spring-security-test:提供測試 Spring Security 的支持,包括 mock 認證和授權。
  • org.springframework.boot:spring-boot-starter-test:包括測試 Spring Boot 應用程序的依賴項和自動配置,如 JUnit、Hamcrest 和 Mockito。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31

    <dependencies>
    <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>5.5.0</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.24.2</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

用 jacoco 設置測試覆蓋率

pom.xml 中加入 jacoco 的 plugin,用於生成測試覆蓋率報告。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

在 ci/cd 中加入 jacoco , 用 gitlab 上傳 sonarqube 進行測試覆蓋率檢查

以下額外加入了 owasp_dependency_check 用於檢查依賴的安全性,以及 sonarqube_check 用於檢查代碼質量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
stages:
- build
- test
- dependency-check
- sonarqube-check

services:
- name: docker:26-dind
pull_policy: if-not-present

variables:
DOCKER_TLS_CERTDIR: ""
GIT_DEPTH: "0"

cache:
paths:
- maven/.m2

build:
image: maven:3.8.6-openjdk-17
stage: build
script:
- mvn clean package -DskipTests
artifacts:
when: on_success
expire_in: 7 days
paths:
- target/*.jar

test:
image: maven:3.8.6-openjdk-17
stage: test
script:
- mvn clean test jacoco:report
artifacts:
when: on_success
expire_in: 7 days
paths:
- target/site/jacoco
- target/surefire-reports
reports:
junit: target/surefire-reports/*.xml
coverage_report:
coverage_format: cobertura
path: target/site/jacoco/jacoco.xml

owasp_dependency_check:
image:
name: registry.gitlab.com/gitlab-ci-utils/docker-dependency-check:latest
entrypoint: [""]
stage: dependency-check
script:
- mkdir -p $CI_PROJECT_DIR/dependency-check
- echo '<?xml version="1.0" encoding="UTF-8"?><suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd"></suppressions>' > suppression.xml
- /usr/share/dependency-check/bin/dependency-check.sh --scan "./" --format "ALL" --project "$CI_PROJECT_NAME" --out "$CI_PROJECT_DIR/dependency-check" --failOnCVSS 0 --suppression suppression.xml --noupdate || true
- if [ $(grep -c "vulnerabilities" dependency-check-report.json) -gt 0 ]; then exit 2; fi
allow_failure: true
artifacts:
when: always
expire_in: 7 days
paths:
- dependency-check/*
reports:
dependency_scanning: dependency-check/dependency-check-report.json
cache:
key: "${CI_JOB_NAME}-${CI_COMMIT_REF_SLUG}"
paths:
- .dependency-check-cache
rules:
- if: '$CI_PIPELINE_SOURCE == "push"'

sonarqube_check:
stage: sonarqube-check
image: maven:3.8.6-openjdk-17
dependencies:
- test
- owasp_dependency_check
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
script:
- mvn sonar:sonar \
-Dsonar.host.url=http://192.168.168.130:9000 \
-Dsonar.qualitygate.wait=true \
-Dsonar.projectName=generic_project \
-Dsonar.projectKey=generic_project \
-Dsonar.dependencyCheck.summarize=true \
-Dsonar.dependencyCheck.jsonReportPath=$CI_PROJECT_DIR/dependency-check/dependency-check-report.json \
-Dsonar.dependencyCheck.xmlReportPath=$CI_PROJECT_DIR/dependency-check/dependency-check-report.xml \
-Dsonar.dependencyCheck.htmlReportPath=$CI_PROJECT_DIR/dependency-check/dependency-check-report.html \
-Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml \
-Dsonar.qualitygate.timeout=300 \
-Dsonar.verbose=true || echo "SonarQube analysis completed with issues"
allow_failure: true
cache:
key: "${CI_JOB_NAME}-${CI_COMMIT_REF_SLUG}"
paths:
- maven/.m2
- .sonar/cache
rules:
- if: '$CI_PIPELINE_SOURCE == "push"'

after_script:
- echo "End CI"

測試springSecurity 保護下的API

使用自定義 annotation @WithMockUser 來給予權限

利用工廠模式,建立自定義的 WithSecurityContextFactory 類別,並實現 createSecurityContext 方法,以便在測試中使用自定義的 @WithMockCustomUser 注解。

1
2
3
4
5
6
7
8
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {
String username() default "test@example.com";
String password() default "password";
String[] roles() default {"USER"};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {

@Override
public SecurityContext createSecurityContext(WithMockCustomUser annotation) {
SecurityContext context = SecurityContextHolder.createEmptyContext();

Collection<GrantedAuthority> authorities = Arrays.stream(annotation.roles())
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());

Authentication auth = new TestingAuthenticationToken(
annotation.username(),
annotation.password(),
authorities
);

context.setAuthentication(auth);
return context;
}
}

測試類別中使用 @WithMockCustomUser 注解

在測試類別中使用 @WithMockCustomUser 注解,模擬使用者登入。
在測試類別中使用 @MockBean 注解,模擬 service 的行為。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = HelloController.class)
class HelloControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private HelloService helloService;

@Test
@WithMockCustomUser
void testHello() throws Exception {
when(helloService.sayHello()).thenReturn("Hello, World!");

mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("Hello, World!"));
}
}

Mock Test with Spring Security
https://shengshengyang.github.io/2025/02/21/mock-test/
作者
Dean Yang
發布於
2025年2月21日
許可協議