Spring Boot 我写的mockMVC测试代码调用其他控制器的API(也许)

ou6hu8tu  于 2023-05-17  发布在  Spring
关注(0)|答案(1)|浏览(89)

首先我请求你理解我可怜的工程师。
我正在为Spring Rest Docs库编写make API规范的测试代码。

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.10'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
    id "org.asciidoctor.jvm.convert" version "3.3.2"
}

group = 'com.jogijo'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

compileJava.options.encoding = 'UTF-8'

configurations {
    asciidoctorExt
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'com.vladmihalcea:hibernate-types-52:2.17.3'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // JPA
    implementation 'org.apache.httpcomponents:httpcore:4.4.15'
    implementation 'org.apache.httpcomponents:httpclient:4.5.13'

    //implementation 'org.springframework.boot:spring-boot-starter-log4j2'
    implementation 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.2'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'

    // 유효성
    implementation 'org.springframework.boot:spring-boot-starter-validation'

    // model struct
    implementation 'org.mapstruct:mapstruct:1.5.4.Final'
    annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.4.Final'

    // security
    implementation('org.springframework.boot:spring-boot-starter-security')

    // DB
    //runtimeOnly ('mysql:mysql-connector-java:8.0.32') //mysql8
    runtimeOnly("com.mysql:mysql-connector-j")
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    //implementation'mysql:mysql-connector-java'

    // mybatis
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.3.0'

    // aws s3
    implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

    //jwt
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
    implementation 'io.jsonwebtoken:jjwt-api:0.11.5'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'

    // Validation
    implementation 'org.springframework.boot:spring-boot-starter-validation'

    //oauth
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

    // Spring Rest Docs
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
    asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
}

ext {
    snippetsDir = file('build/generated-snippets')
}

tasks.named('test') {
    outputs.dir snippetsDir
    useJUnitPlatform()
}

asciidoctor {
    inputs.dir snippetsDir
    configurations 'asciidoctorExt'
    dependsOn test

}

task copyDocument(type: Copy) {
    dependsOn asciidoctor
    doFirst{
        delete file('src/main/resources/static/docs')
    }
    from file("build/docs/asciidoc")
    into file("src/main/resources/static/docs")
}

build {
    dependsOn copyDocument
}

bootJar {
    dependsOn asciidoctor
    from("${asciidoctor.outputDir}/html5") {
        into 'static/docs'
    }
}

这是build.gradle

@RestController
@RequestMapping("/app/alarms")
@RequiredArgsConstructor
public class AlarmController {
    private final AlarmService alarmService;
    private final AlarmProvider alarmProvider;
    private final JwtService jwtService;

    /**
     * 알람 1개 불러오기
     * @param alarmId
     * @return
     */
    @GetMapping("/{alarmId}")
    public BaseResponse<AlarmRes> GetAlarm(@PathVariable Integer alarmId){
        AlarmRes alarmRes = alarmProvider.getAlarm(alarmId);

        return new BaseResponse<>(ResponseStatus.SUCCESS, alarmRes);
    }

这是我想要测试的API。

package com.wakeUpTogetUp.togetUp.alarms;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wakeUpTogetUp.togetUp.alarms.dto.response.AlarmRes;
import com.wakeUpTogetUp.togetUp.common.ResponseStatus;
import com.wakeUpTogetUp.togetUp.common.dto.BaseResponse;
import com.wakeUpTogetUp.togetUp.utils.JwtService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;

import static org.mockito.BDDMockito.*;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
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.*;

@WebMvcTest(AlarmController.class)
@AutoConfigureRestDocs
class AlarmControllerTest {
    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private ObjectMapper objectMapper;
    @MockBean
    private AlarmService alarmService;
    @MockBean
    private AlarmProvider alarmProvider;
    @MockBean
    private JwtService jwtService;

    @Test
    @DisplayName("getAlarm - [Get] /alarm/{alarmId}")
    void getAlarm() throws Exception{
        //given
        AlarmRes response = AlarmRes.builder()
                .id(42)
                .userId(9)
                .missionId(1)
                .name("기상알람")
                .icon("⏰")
                .sound("default")
                .volume(80)
                .isVibrate(true)
                .isRoutineOn(true)
                .snoozeInterval(5)
                .snoozeCnt(3)
                .startHour(6)
                .startMinute(0)
                .monday(true)
                .tuesday(true)
                .wednesday(true)
                .thursday(true)
                .friday(true)
                .saturday(true)
                .sunday(false)
                .isActivated(true)
                .build();

        given(alarmProvider.getAlarm(42)).willReturn(response);

        Integer alarmId = 42;

        //when
        ResultActions action = mockMvc.perform(get("/app/alarms/42"))
                .andDo(print());

        //then
        BaseResponse<AlarmRes> responseData = new BaseResponse<>(ResponseStatus.SUCCESS, response);

        action.andExpect(status().isOk())
                .andExpect(content().json(objectMapper.writeValueAsString(responseData)))
                .andExpect(jsonPath("$.result.userId").value(9))
                .andDo(
                        // rest docs 문서 작성 시작
                        document("alarm/getAlarm", // directory명 위에서 설정한 build/generated-snippets 하위에 생성
                                // --> build/generated-snippets/member/create
                                requestParameters( // queryString 관련 변수 정보 입력
                                        parameterWithName("alarmId").description("알람 Id")
                                ),
                                responseFields( // response data 필드 정보 입력
                                        fieldWithPath("httpStatusCode").description("http 상태코드"),
                                        fieldWithPath("httpReasonPhrase").description("http 상태코드 설명문구"),
                                        fieldWithPath("message").description("설명 메시지"),
                                        subsectionWithPath("result").description("결과"),
                                        fieldWithPath("id").description("알람 Id"),
                                        fieldWithPath("userId").description("사용자 Id")
                                )
                        )
                );
    }

这是测试代码。

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /app/alarms/42
       Parameters = {}
          Headers = []
             Body = null
    Session Attrs = {SPRING_SECURITY_SAVED_REQUEST=DefaultSavedRequest [http://localhost:8080/app/alarms/42]}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 302
    Error message = null
          Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY", Location:"http://localhost:8080/oauth2/authorization/google"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = [HERE IS REDIRECT URL BUT IT COULD BE SPAM POST. LOCALHOST:8080 / OAUTH2
AUTHORIZATION / GOOGLE]
          Cookies = []

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /app/alarms/42
       Parameters = {}
          Headers = []
             Body = null
    Session Attrs = {SPRING_SECURITY_SAVED_REQUEST=DefaultSavedRequest [http://localhost:8080/app/alarms/42]}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 302
    Error message = null
          Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY", Location:"http://localhost:8080/oauth2/authorization/google"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = http://localhost:8080/oauth2/authorization/google
          Cookies = []

Status expected:<200> but was:<302>
Expected :200
Actual   :302
<Click to see difference>

java.lang.AssertionError: Status expected:<200> but was:<302>

AlarmControllerTest > getAlarm - [Get] /alarm/{alarmId} FAILED
    java.lang.AssertionError at AlarmControllerTest.java:79
1 test completed, 1 failed
> Task :test FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///C:/Users/anyti/IdeaProjects/TogetUp/build/reports/tests/test/index.html
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 9s
4 actionable tasks: 2 executed, 2 up-to-date

这是错误代码。
有google oauth登录代码重定向到url。但我不知道为什么这个测试代码会得到这样的响应。它们在不同的控制器中,并且没有回复URL“http://localhost:8080/oauth2/authorization/google”的API
我注解了所有与oauth相关的代码,但它不起作用。我检查了URL,但我认为它是正确的。
我现在真的累坏了。我很高兴如果有人帮助我。

6yt4nkrj

6yt4nkrj1#

您的问题中缺少安全配置,但您显然已将应用程序配置为OAuth2客户端(使用Google作为授权服务器),并重定向到登录请求,该请求具有没有授权客户端的会话。
您被重定向到登录,因为您没有为测试请求设置或模拟安全上下文。我建议你读一下我写的Baeldung article
在您的情况下,可能需要的是:

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login;

...
    get("/app/alarms/42").with(oauth2Login()/* configure user identity details there */)

另一种选择是使用this libs I wrote,并用@WithOAuth2Login@WithOidcLogin装饰测试方法或类。

相关问题