[에러해결] MockMvc 테스트 중 만난 InvalidUseOfMatchersException

Walter Mitty·2024년 1월 4일
0

Spring

목록 보기
18/19

서론

오랜만에 MockMvc + RestDocs로 API 문서를 만들려고 하다보니 다 까먹어서 에러를 만나게되었다.
에러에 대한 해결 방법까지 ㄱㄱ

본론

에러

  • 1트 : NPE
  • 2트 : InvalidUseOfMatchersException
  • 3트 : 성공!

코드

package com.kaii.dth.presentation.student;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kaii.dth.domaincore.domain.shared.Expression;
import com.kaii.dth.domaincore.domain.shared.RegistrationStatus;
import com.kaii.dth.domaincore.domain.shared.UserRole;
import com.kaii.dth.domaincore.domain.student.Student;
import com.kaii.dth.domaincore.domain.student.StudentResult;
import com.kaii.dth.domaincore.infrastructure.exception.message.ResponseMessage;
import com.kaii.dth.port.user.UserReaderPort;
import com.kaii.dth.presentation.shared.ControllerTest;
import com.kaii.dth.presentation.student.dto.StudentDTO;
import com.kaii.dth.usecase.student.StudentUseCase;
import com.kaii.dth.util.TokenUtil;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import static com.kaii.dth.common.ApiDocumentUtils.getDocumentRequest;
import static com.kaii.dth.common.ApiDocumentUtils.getDocumentResponse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(StudentController.class)
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
public class StudentControllerTest extends ControllerTest {

    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private ObjectMapper objectMapper;
    @MockBean
    private StudentUseCase studentUseCase;
    @MockBean
    private UserReaderPort userReaderPort;
    @MockBean
    private TokenUtil tokenUtil;

    @Test
    @WithMockUser
    void 학생_마이페이지_수정_테스트() throws Exception {
        // given
        String accessToken = "ThisIsAccessToken";

        Student student = Student.builder()
                .userId(1L)
                .role(UserRole.ROLE_STUDENT)
                .loginId("S000001")
                .password("2023")
                .verificationNum(null)
                .verifyNumSendCnt(Expression.VERIFICATION_NUM_SEND_CNT_DEFAULT)
                .verifyNumIssuanceTime(null)
                .name("임학생")
                .email("email@email.com")
                .phoneNum("01000000000")
                .isFirstSetPw(Expression.IS_FIRST_SET_PW_DEFAULT)
                .isAgreeTos(Expression.IS_AGREE_TOS_DEFAULT)
                .agreeTosDate(null)
                .loginTryCnt(Expression.LOGIN_TRY_COUNT_DEFAULT)
                .pwChangeDate(null)
                .refreshToken(null)
                .deactivatedAt(null)
                .registrationStatus(RegistrationStatus.ATTENDING)
                .year(2023)
                .semester(1)
                .schoolYear(1)
                .patientList(null)
                .build();

        StudentDTO.EditReuest editReuest = new StudentDTO.EditReuest("studentEditTest@email.com", "01020240104");

        given(여기!!!).willReturn(여기);
        
        // when
        mockMvc.perform(
                put("/student-mypage")
                        .header("Authorization", accessToken)
                        .accept(MediaType.APPLICATION_JSON)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(editReuest))
        )
                .andExpect(status().isOk())
                .andExpect(jsonPath("code").value(200))
                .andExpect(jsonPath("message").value(ResponseMessage.SUCCESS_MSG))
                .andExpect(jsonPath("data").value("임학생 - 수정 완료"))
                .andDo(document(
                        "student-mypage/modify",
                        getDocumentRequest(),
                        getDocumentResponse(),
                        requestHeaders(
                                headerWithName("Authorization").description("액세스 토큰")
                        ),
                        requestFields(
                                fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"),
                                fieldWithPath("phoneNum").type(JsonFieldType.STRING).description("이메일").optional()
                        ),
                        responseFields(
                                fieldWithPath("code").type(JsonFieldType.NUMBER).description("결과 코드"),
                                fieldWithPath("message").type(JsonFieldType.STRING).description("결과 메세지"),
                                fieldWithPath("data").type(JsonFieldType.STRING).description("학생(본인) 이름 - 수정 완료")
                        )
                ));
    }
}

1트

given(studentUseCase.updateMyPage(accessToken, editReuest.toCommand())).willReturn(student);

에러

Request processing failed: java.lang.NullPointerException: Cannot invoke "com.kaii.dth.domaincore.domain.student.Student.getName()" because the return value of "com.kaii.dth.usecase.student.StudentUseCase.updateMyPage(String, com.kaii.dth.domaincore.domain.student.StudentCommand$Edit)" is null

2트

given(studentUseCase.updateMyPage(anyString(), any())).willReturn(any());
//or
given(studentUseCase.updateMyPage(any(), any())).willReturn(any());
jakarta.servlet.ServletException: Request processing failed: org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at com.~~~

This exception may occur if matchers are combined with raw values:
    //incorrect:
    someMethod(any(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
    //correct:
    someMethod(any(), eq("String by matcher"));

에러를 잘 읽어보면 "2개의 Matchers를 예상, 1개만 기록" 이라고 되어있다.

또한 "Matchers가 raw한 값과 결합된 경우 이 예외가 발생할 수 있음" 인데,

Usecase 메서드에 파라미터를 넘겨줄 때 argumentmatcher를 한 인자에 사용하면 다른 모든 인자도 argumentmatcher로 넘겨주어야 한다는 것이다. <라는 글을 보았다.

그러나, 위에서 다 any()로 넘겨주었는데도 에러가 발생한거니..뭐 어떻게 해야하나?

3트(해결 코드)

given(studentUseCase.updateMyPage(any(), any())).willReturn(student);

드디어 성공!

0개의 댓글