Troubleshooting: 무엇이 문제였는가?/플러스 프로젝트

10단계: "NoSuchBeanDefinitionException - No qualifying bean of type 'JwtUtil' available"

writingforever162 2025. 1. 28. 22:53

[인용 및 참고 출처]

1. 구글 검색: 티스토리, "@Slf4j "테스트 코드", 테스트 코드에서 @Slf4j 사용하기, (2025.01.28) 

2. 구글 검색: SLF4J Manual, "Spring Slf4j docs", SLF4J user manual, (2025.01.28)

 

[문제]

더보기
package org.example.expert;

import java.time.LocalDateTime;
import org.example.expert.common.dto.AuthUserDto;
import org.example.expert.common.entity.Todo;
import org.example.expert.domain.user.dto.response.UserResponseDto;
import org.example.expert.common.entity.User;
import org.example.expert.common.enums.UserRole;
import org.springframework.test.util.ReflectionTestUtils;

public class SharedData {

    public static final AuthUserDto AUTH_USER_DTO = new AuthUserDto(
        1L,
        "user1@test.com",
        UserRole.USER,
        "사용자1"
    );
    public static final User USER = User.fromAuthUser(AUTH_USER_DTO);
    public static final UserResponseDto USER_RESPONSE_DTO = new UserResponseDto(USER);
    public static final Todo TODO = new Todo(
        "일정 제목",
        "일정 내용",
        "날씨",
        USER
    );

    static {
        ReflectionTestUtils.setField(
            TODO,
            "id",
            1L
        );

        ReflectionTestUtils.setField(
            TODO,
            "createdAt",
            LocalDateTime.of(
                2025,
                1,
                15,
                10,
                0,
                0,
                401000000
            )
        );

        ReflectionTestUtils.setField(
            TODO,
            "updatedAt",
            LocalDateTime.of(2025,
                1,
                15,
                10,
                0,
                0,
                401000000
            )
        );
    }
}
더보기
package org.example.expert.domain.todo.controller;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.example.expert.SharedData;
import org.example.expert.common.exception.notfound.TodoNotFoundException;
import org.example.expert.domain.todo.dto.response.TodoResponseDto;
import org.example.expert.domain.todo.service.TodoService;
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.test.web.servlet.MockMvc;

@WebMvcTest(value = TodoController.class)

class TodoControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TodoService todoService;

    @Test
    void todo_단건_조회에_성공한다() throws Exception {
        // given
        TodoResponseDto responseDto = new TodoResponseDto(
            SharedData.TODO,
            SharedData.USER_RESPONSE_DTO
        );

        long todoId = responseDto.getId();
        String title = responseDto.getTitle();
        String contents = responseDto.getContents();
        String weather = responseDto.getWeather();
        long userId = responseDto.getUser().getId();
        String email = responseDto.getUser().getEmail();
        String createdAt = responseDto.getCreatedAt().toString();
        String updatedAt = responseDto.getUpdatedAt().toString();

        // when
        when(todoService.getTodo(todoId)).thenReturn(responseDto);

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(todoId))
            .andExpect(jsonPath("$.title").value(title))
            .andExpect(jsonPath("$.contents").value(contents))
            .andExpect(jsonPath("$.weather").value(weather))
            .andExpect(jsonPath("$.user.id").value(userId))
            .andExpect(jsonPath("$.user.email").value(email))
            .andExpect(jsonPath("$.createdAt").value(createdAt))
            .andExpect(jsonPath("$.updatedAt").value(updatedAt));
    }

    @Test
    void todo_단건_조회_시_todo가_존재하지_않아_예외가_발생한다() throws Exception {
        // given
        long todoId = 2L;

        // when
        when(todoService.getTodo(todoId))
            .thenThrow(new TodoNotFoundException());

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId))
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.errorCode").value("ERRNF02"))
            .andExpect(jsonPath("$.errorMessage").value("Todo is not found."));
    }
}

Spring Security 적용 후 테스트 코드를 실행하니 테스트 성공 실패 여부가 아니라 오류가 발생했다. 무언가 설정이 되었을 거란 생각으로 오류 메시지를 찬찬히 읽었다.

 

[원인]

NoSuchBeanDefinitionException: 
// Spring이 특정 타입의 빈(bean)을 찾지 못해서 발생한 예외

No qualifying bean of type 'org.example.expert.common.utils.JwtUtil' available: 
// [1] org.example.expert.common.utils.JwtUtil:
//     Spring이 찾으려고 한 타입의 빈
// [2] 문제:
//     해당 타입의 빈이 application context에 등록되지 않음
//     등록 시 사용할 수 있는 어노테이션(annotation)
//     → @Component, @Service, 또는 @Configuration
// [3] 오류 메시지 의미:      
//     JwtUtil 클래스가 빈으로 등록되지 않은 상태로 의존성 주입을 시도함

expected at least 1 bean which qualifies as autowire candidate. 
// [의미]
//  Spring은 최소한 적합한 빈이 하나는 있어야 한다고 예상함
//  → 'JwtUtil' 타입에 해당하는 빈이 application context에 있어야 하는데,
//    빈이 등록되지 않아서 Spring이 해당 빈을 주입할 수 없음

Dependency annotations: {}
// JwtUtil을 주입하려는 곳에 의존성 주입 어노테이션이 없거나 잘못 설정됨
// 의존성 주입 어노테이션 예시:
// → @Autowired, @Inject
// → 해당 어노테이션이 없으면 Spring은 해당 필드를 빈으로 자동 주입할 수 없음

위와 같이 오류 메시지를 읽은 후, JwtUtil 클래스를 빈(bean)으로 주입했다. 주입 방식에는 두 가지가 있었다.

// [1/2] @Import 어노테이션으로 빈(bean) 주입
@Import(JwtUtil.class)
@WebMvcTest(value = TodoController.class)
class TodoControllerTest {
더보기
package org.example.expert.domain.todo.controller;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.example.expert.SharedData;
import org.example.expert.common.exception.notfound.TodoNotFoundException;
import org.example.expert.common.utils.JwtUtil;
import org.example.expert.domain.todo.dto.response.TodoResponseDto;
import org.example.expert.domain.todo.service.TodoService;
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.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;

@Import(JwtUtil.class)
@WebMvcTest(value = TodoController.class)
class TodoControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TodoService todoService;

    @Test
    void todo_단건_조회에_성공한다() throws Exception {
        // given
        TodoResponseDto responseDto = new TodoResponseDto(
            SharedData.TODO,
            SharedData.USER_RESPONSE_DTO
        );

        long todoId = responseDto.getId();
        String title = responseDto.getTitle();
        String contents = responseDto.getContents();
        String weather = responseDto.getWeather();
        long userId = responseDto.getUser().getId();
        String email = responseDto.getUser().getEmail();
        String createdAt = responseDto.getCreatedAt().toString();
        String updatedAt = responseDto.getUpdatedAt().toString();

        // when
        when(todoService.getTodo(todoId)).thenReturn(responseDto);

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(todoId))
            .andExpect(jsonPath("$.title").value(title))
            .andExpect(jsonPath("$.contents").value(contents))
            .andExpect(jsonPath("$.weather").value(weather))
            .andExpect(jsonPath("$.user.id").value(userId))
            .andExpect(jsonPath("$.user.email").value(email))
            .andExpect(jsonPath("$.createdAt").value(createdAt))
            .andExpect(jsonPath("$.updatedAt").value(updatedAt));
    }

    @Test
    void todo_단건_조회_시_todo가_존재하지_않아_예외가_발생한다() throws Exception {
        // given
        long todoId = 2L;

        // when
        when(todoService.getTodo(todoId))
            .thenThrow(new TodoNotFoundException());

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId))
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.errorCode").value("ERRNF02"))
            .andExpect(jsonPath("$.errorMessage").value("Todo is not found."));
    }
}
// [2/2] @WebMvcTest와 includeFilters로 특정 빈(bean)을 주입하기
@WebMvcTest(
    value = TodoController.class, // 테스트할 클래스 지정
    includeFilters = @ComponentScan.Filter(
        type = FilterType.REGEX, 
        // 정규식(regex)을 이용해 빈을 필터링
        pattern = "org.example.expert.common.utils.JwtUtil"
        // 포함하려는 빈의 경로에 맞는 빈을 필터링
        // 빈의 경로: 패키지 이름 + 클래스 이름
        // JwtUtil 클래스만 포함함
    )
)
class TodoControllerTest {
더보기
package org.example.expert.domain.todo.controller;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.example.expert.SharedData;
import org.example.expert.common.exception.notfound.TodoNotFoundException;
import org.example.expert.domain.todo.dto.response.TodoResponseDto;
import org.example.expert.domain.todo.service.TodoService;
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.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest(
    value = TodoController.class,
    includeFilters = @ComponentScan.Filter(
        type = FilterType.REGEX,
        pattern = "org.example.expert.common.utils.JwtUtil"
    )
)
class TodoControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TodoService todoService;
    
    @Test
    void todo_단건_조회에_성공한다() throws Exception {
        // given
        TodoResponseDto responseDto = new TodoResponseDto(
            SharedData.TODO,
            SharedData.USER_RESPONSE_DTO
        );

        long todoId = responseDto.getId();
        String title = responseDto.getTitle();
        String contents = responseDto.getContents();
        String weather = responseDto.getWeather();
        long userId = responseDto.getUser().getId();
        String email = responseDto.getUser().getEmail();
        String createdAt = responseDto.getCreatedAt().toString();
        String updatedAt = responseDto.getUpdatedAt().toString();

        // when
        when(todoService.getTodo(todoId)).thenReturn(responseDto);

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(todoId))
            .andExpect(jsonPath("$.title").value(title))
            .andExpect(jsonPath("$.contents").value(contents))
            .andExpect(jsonPath("$.weather").value(weather))
            .andExpect(jsonPath("$.user.id").value(userId))
            .andExpect(jsonPath("$.user.email").value(email))
            .andExpect(jsonPath("$.createdAt").value(createdAt))
            .andExpect(jsonPath("$.updatedAt").value(updatedAt));
    }

    @Test
    void todo_단건_조회_시_todo가_존재하지_않아_예외가_발생한다() throws Exception {
        // given
        long todoId = 2L;

        // when
        when(todoService.getTodo(todoId))
            .thenThrow(new TodoNotFoundException());

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId))
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.errorCode").value("ERRNF02"))
            .andExpect(jsonPath("$.errorMessage").value("Todo is not found."));
    }
}

이렇게 둘 중 하나를 골라서 JwtUtil 클래스를 주입한 후 테스트 코드를 다시 실행한 결과는 아래 사진과 같았다.

그렇다. 

 

테스트 코드를 실행할 때 로그인한 사용자 없어 모든 테스트에서 401 Unauthorized 오류가 발생했다. 테스트용 JWT를 직접 작성해 사용하는 방법도 있었지만, 이 테스트 코드의 목적은 컨트롤러(Controller)가 요청을 올바르게 처리하는지 확인하기였으므로 다음과 같은 순서로 문제를 해결했다.

// [1/4] 인증 관련 기능에 필요한 빈(Bean) 주입
@Import({JwtUtil.class, JwtFilter.class, SecurityConfig.class})
@WebMvcTest(value = TodoController.class)
class TodoControllerTest {

우선 WebMvcTest는 컨트롤러 테스트에 필요한 컴포넌트(component)만 스캔(scan)하여 불필요한 빈을 불러오지 않고 테스트를 빠르게 실행할 수 있었지만, @Configuration이 붙은 빈은 자동으로 포함되지 않는 제약이 있었다. 이런 이유로 JWT 인증과 관련된 JwtUtil, JwtFilter, SecurityConfig 빈이 테스트 환경에 제대로 주입되지 않아 인증 관련 기능을 테스트 코드에서 실행할 수 없었다. 이 문제는 @Import를 사용해 각 클래스를 직접 빈으로 주입하여 해결했다.

// [2/4] JwtFilter 수정하기 [수정 전]
if (bearerJwt == null) {
    response.sendError(
        HttpServletResponse.SC_BAD_REQUEST,
        "JWT 토큰이 필요합니다."
    );
    return;
}
// [2/4] JwtFilter 수정하기 [수정 후]
if (bearerJwt == null) {
    filterChain.doFilter(request, response);
    return;
}

그 다음으로는 JwtFilter에서 Jwt가 null일 때 처리하는 부분을 수정했다. 원래 Jwt가 null일 땐 response.sendError()로 Jwt 토큰이 필요하다는 BAD_REQUEST(400) 응답을 보냈지만, 수정 후에는 'filterChain.doFilter(request, response)'를 호출하여, 토큰이 없으면 기존의 JwtFilter 대신 Spring Security에서 요청을 처리하도록 했다.

// [3/4] build.gradle에 의존성 추가하기 
testImplementation 'org.springframework.security:spring-security-test'

JwtFilter를 수정한 뒤에는 build.gradle에 Spring Security Test 의존성을 추가했다.

// [4/4] @WithMockUser 어노테이션 추가하기 
@WithMockUser
@Test
void todo_단건_조회에_성공한다() throws Exception {
// [4/4] @WithMockUser 어노테이션 추가하기 
@WithMockUser
@Test
void todo_단건_조회_시_todo가_존재하지_않아_예외가_발생한다() throws Exception {

Spring Security Test 의존성을 추가한 다음에는 테스트 메서드(method)마다 '@WithMockUser' 어노테이션(annotation)을 추가해, 가짜 사용자를 인증 처리해서 '토큰이 없다'는 예외가 발생하지 않도록 했다.

 

[해결]

더보기
package org.example.expert.domain.todo.controller;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.example.expert.SharedData;
import org.example.expert.common.config.SecurityConfig;
import org.example.expert.common.exception.notfound.TodoNotFoundException;
import org.example.expert.common.filter.JwtFilter;
import org.example.expert.common.utils.JwtUtil;
import org.example.expert.domain.todo.dto.response.TodoResponseDto;
import org.example.expert.domain.todo.service.TodoService;
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.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

@Import({JwtUtil.class, JwtFilter.class, SecurityConfig.class})
@WebMvcTest(value = TodoController.class)
class TodoControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TodoService todoService;

    @WithMockUser
    @DisplayName("할 일 단건 조회 - 성공: 유효한 ID로 조회")
    @Test
    void todo_단건_조회에_성공한다() throws Exception {
        // given
        TodoResponseDto responseDto = new TodoResponseDto(
            SharedData.TODO,
            SharedData.USER_RESPONSE_DTO
        );

        long todoId = responseDto.getId();
        String title = responseDto.getTitle();
        String contents = responseDto.getContents();
        String weather = responseDto.getWeather();
        long userId = responseDto.getUser().getId();
        String email = responseDto.getUser().getEmail();
        String createdAt = responseDto.getCreatedAt().toString();
        String updatedAt = responseDto.getUpdatedAt().toString();

        // when
        when(todoService.getTodo(todoId)).thenReturn(responseDto);

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(todoId))
            .andExpect(jsonPath("$.title").value(title))
            .andExpect(jsonPath("$.contents").value(contents))
            .andExpect(jsonPath("$.weather").value(weather))
            .andExpect(jsonPath("$.user.id").value(userId))
            .andExpect(jsonPath("$.user.email").value(email))
            .andExpect(jsonPath("$.createdAt").value(createdAt))
            .andExpect(jsonPath("$.updatedAt").value(updatedAt));
    }

    @WithMockUser
    @DisplayName("할 일 단건 조회 - 실패: 존재하지 않는 ID로 조회")
    @Test
    void todo_단건_조회_시_todo가_존재하지_않아_예외가_발생한다() throws Exception {
        // given
        long todoId = 2L;

        // when
        when(todoService.getTodo(todoId))
            .thenThrow(new TodoNotFoundException());

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId))
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.errorCode").value("ERRNF02"))
            .andExpect(jsonPath("$.errorMessage").value("Todo is not found."));
    }
}

문제가 발생한 원인을 찾고 순서대로 해결한 뒤, 테스트 코드를 실행하니 마침내 테스트 성공 결과를 얻을 수 있었다. 테스트 코드가 잘 실행되는지 확인한 후에는 @WithMockUser 어노테이션 설명을 읽었다. 

 

[@WithMockUser 설명]

이 어노테이션은 WithSecurityContextTestExecutionListener와 함께 사용될 때 테스트 메서드에 추가되어 가짜 사용자로 인증된 상태에서 해당 테스트 메서드가 실행되도록 하며, Spring Security 4.0부터 제공된다. MockMvc와 함께 사용될 때 설정되는 SecurityContext의 속성은 다음과 같다.

 

(1) SecurityContext는 SecurityContextHolder.createEmptyContext()로 생성된다. 

 

(2) SecurityContext는 UsernamePasswordAuthenticationToken으로 이루어지며 이 토큰에 포함된 정보는 다음과 같다.

 username: 어노테이션에서 지정한 value() 또는 username() 값

GrantedAuthority: 어노테이션에서 지정한 roles() 값

password: 어노테이션에서 지정한 password() 값

 

[추가 학습]

설명을 읽었을 때, @WithMockUser 어노테이션 값을 별도로 설정하지 않으면 기본값이 무엇인지 궁금해져서 @Sl4fj 어노테이션을 사용하여 확인했다.

// [1/4] build.gradle에 의존성 추가하기
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
// [2/4] @Slf4j 어노테이션 추가하기
@Slf4j
@Import({JwtUtil.class, JwtFilter.class, SecurityConfig.class})
@WebMvcTest(value = TodoController.class)
class TodoControllerTest {
// [3/4] 테스트 코드 안에서 UserDetails 값을 가져오기
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext()
    .getAuthentication().getPrincipal();

// [4/4] 테스트 코드 안에서 로그 입력하기
log.info("Username: {}", userDetails.getUsername());
log.info("GrantedAuthority: {}", userDetails.getAuthorities());
log.info("Password: {}", userDetails.getPassword());
더보기
/**
 * Obtain the current 'SecurityContext'.
 * == 현재의 SecurityContext를 반환한다.
 * == 인증된 사용자나 보안 관련 정보를 포함하는 객체를 가져온다.
 *
 * @return the security context (never 'null')
 * == 항상 SecurityContext를 반환하며, 그 값은 null이 절대 아니다.
 */
public static SecurityContext getContext() {
    return strategy.getContext();
}
/**
 * Obtains the currently authenticated principal,
 * or an authentication request token.
 * 현재 인증된 'Principal' 객체 또는 인증 요청 토큰을 반환한다.
 *
 * @return the 'Authentication' or 'null' 
 * if no authentication information is available
 * == 'Authentication' 객체 또는 인증 정보가 없으면 null 값을 반환한다.
 */
Authentication getAuthentication();
/**
* @return the 'Principal' being authenticated 
* or the authenticated principal after authentication.
* == 인증 중이거나 인증이 완료된 'Principal' 객체를 반환한다.
*/
Object getPrincipal();
더보기
package org.example.expert.domain.todo.controller;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import lombok.extern.slf4j.Slf4j;
import org.example.expert.common.config.SecurityConfig;
import org.example.expert.common.exception.notfound.TodoNotFoundException;
import org.example.expert.common.filter.JwtFilter;
import org.example.expert.common.utils.JwtUtil;
import org.example.expert.domain.todo.service.TodoService;
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.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

@Slf4j
@Import({JwtUtil.class, JwtFilter.class, SecurityConfig.class})
@WebMvcTest(value = TodoController.class)
class TodoControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TodoService todoService;

    @WithMockUser
    @DisplayName("할 일 단건 조회 - 실패: 존재하지 않는 ID로 조회 시 예외 발생")
    @Test
    void todo_단건_조회_시_todo가_존재하지_않아_예외가_발생한다() throws Exception {

        UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext()
            .getAuthentication().getPrincipal();

        log.info("Username: {}", userDetails.getUsername());
        log.info("GrantedAuthority: {}", userDetails.getAuthorities());
        log.info("Password: {}", userDetails.getPassword());

        // given
        long todoId = 2L;

        // when
        when(todoService.getTodo(todoId))
            .thenThrow(new TodoNotFoundException());

        // then
        mockMvc.perform(get("/todos/{todoId}", todoId))
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.errorCode").value("ERRNF02"))
            .andExpect(jsonPath("$.errorMessage").value("Todo is not found."));
    }
}

로그를 확인한 결과, 별도로 값을 설정하지 않은 @WithMockUser 어노테이션의 기본값은 다음과 같다는 점을 알 수 있었다. 

 

username: user

 GrantedAuthority: [ROLE_USER]

 password: password