First I ask for your understanding of my poor eng.
I'm writing test code for make api specification by Spring Rest Docs library.
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'
}
}
this is 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);
}
this is the api that I want to test.
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")
)
)
);
}
this is test code.
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
This is error code.
There is google oauth login code that redirect to url. But I don't know why this test code get that response. They're in different controller and also there's no api that reply the url "http://localhost:8080/oauth2/authorization/google"
I annotated all code related with oauth but It didn't work. and I check the url but It's right I think.
I'm really exhausted now. I would so glad if someone help me.
The security configuration is missing in your question, but you obviously have configured your application as an OAuth2 client (with Google as authorization server), and redirection to login for requests having a session which do not have an authorized client.
You are redirected to login because you didn't set or mock the security context for your test request. I suggest you read this Baeldung article I wrote.
In your case, what might be needed is:
Another option is to use this libs I wrote and decorate your tests methods or classes with
@WithOAuth2Loginor@WithOidcLogin.