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!")); } }
|