[TIL]23.01.31 ~3.11 mockito 이용 단위테스트 시 에러들

hyewon jeong·2023년 1월 30일
0

TIL

목록 보기
79/138

💡 에러 1 Cannot invoke "~" because "this.jwtUtil" is null

1 발생

userService test 중 회원가입은 잘되었으나 로그인때 에러발생함


2 코드

Cannot invoke "com.example.myblog1.common.jwt.JwtUtil.createToken(java.lang.String, com.example.myblog1.user.entity.UserRoleEnum)" because "this.jwtUtil" is null
java.lang.NullPointerException: Cannot invoke "com.example.myblog1.common.jwt.JwtUtil.createToken(java.lang.String, com.example.myblog1.user.entity.UserRoleEnum)" because "this.jwtUtil" is null


3 원인

@Slf4j
@Component
@NoArgsConstructor(force = true,access = AccessLevel.PROTECTED)
@RequiredArgsConstructor
public class JwtUtil {

   ......

    @Value("${jwt.secret.key}")
    private String secretKey;
    .....

    @PostConstruct
    public void init() {
        byte[] bytes = Base64.getDecoder().decode(secretKey);
        key = Keys.hmacShaKeyFor(bytes);
    }

@PostConstructor가 동작하지 않기 때문이다.


4 해결

@PostConstructor가 동작하지 않기 때문에, 임의로 실행시켜야 함
@mock 객체들 선언이 끝난 쪽에 아래코드를 넣어주면 해결 됨

    @BeforeEach
    void prepare() {
        ReflectionTestUtils.setField(jwtUtil, 
                                 "secretKey", // jwtUtil의 secretKey값이 저장될 변수
                                "7ZWt7ZW0OTntmZTsnbTtjIXtlZzqta3snYTrhIjrqLjshLjqs4TroZzrgpjslYTqsIDsnpDtm4zrpa3tlZzqsJzrsJzsnpDrpbzrp4zrk6TslrTqsIDsnpA="); // secretKey의 값
        jwtUtil.init(); // jwtUtil에서 @PostConstructor가 동작하지 않기 때문에, 임의로 실행시켜야 함
    }

💡 에러 2 Strict stubbing argument mismatch. Please check:

1 발생

userController Test 중 생긴 에러


2 코드

Request processing failed; nested exception is org.mockito.exceptions.misusing.PotentialStubbingProblem:
Strict stubbing argument mismatch. Please check:

  • this invocation of 'signup' method:
    userService.signup(
    xcom.example.myblog1.user.dto.📌SignupRequest@e415a66
    );
    -> at com.example.myblog1.user.controller.UserController.signup(UserController.java:32)
  • has following stubbing(s) with different arguments:
    1. userService.signup(
    com.example.myblog1.user.dto.📌SignupRequest@cd2df889
    );
    -> at com.example.myblog1.user.controller.UserControllerTest.signup1(UserControllerTest.java:61)
    Typically, stubbing argument mismatch indicates user mistake when writing tests.
    Mockito fails early so that you can debug potential problem easily.

3 원인

이 목객체로 생성한 리퀘스트와

content(new Gson().toJson(request))); 

이 리퀘스트가 일치 하지 않는다는 말이다.

위의 📌 이 부분을 봐도 객체의주소값이 다르다고 말해주고 있다.
왜 객체의 주소값이 달려졌을까?

  ResultActions resultActions = mockMvc.perform(
                MockMvcRequestBuilders.post("/api/users/signup")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(new Gson().toJson(request))); 

content(new Gson().toJson(request)));

이 소스 코드에서 new를 사용해서 새로운 인스턴스를 만들었기 때문에, 목킹할 때 사용한 아규먼트와 일치하지 않아서 발생할 에러이다.


4 해결

어떤 타입의 postRequest 인스턴스를 받는지 상관없이 내가 미리 만들어둔 postRequest을 리턴하게 목킹하고 싶다면 any()를 사용하면 해결 됨

when(userService.signup(any(SignupRequest.class)))
                .thenReturn(response);

완성코드

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;
    @InjectMocks
    private UserService userService;

    @Spy
    private JwtUtil jwtUtil;

    @BeforeEach
    void prepare() {
        ReflectionTestUtils.setField(jwtUtil,
                "secretKey", // jwtUtil의 secretKey값이 저장될 변수
                "7ZWt7ZW0OTntmZTsnbTtjIXtlZzqta3snYTrhIjrqLjshLjqs4TroZzrgpjslYTqsIDsnpDtm4zrpa3tlZzqsJzrsJzsnpDrpbzrp4zrk6TslrTqsIDsnpA="); // secretKey의 값
        jwtUtil.init(); // jwtUtil에서 @PostConstructor가 동작하지 않기 때문에, 임의로 실행시켜야 함
    }

    @Spy
    private BCryptPasswordEncoder passwordEncoder;


   
    @Test
    @DisplayName("로그인")
    void login() {   // 에러 왜 발생하는지 체크하기 -> 이유 : jwtUtil에서 @PostConstructor가 동작하지 않기 때문에, 임의로 실행시켜주니까 에러해결됨
        //given
        LoginRequest request = LoginRequest.builder()
                .username("pororo")
                .password("12341234")
                .build();
        MockHttpServletResponse servletResponse = new MockHttpServletResponse();//웹서버가 관장하는 값을 넣어줌

        //목객체의 리턴값
        User user = new User("pororo", passwordEncoder.encode("12341234"), "pororo@naver.com", UserRoleEnum.USER);
        when(userRepository.findByUsername(any(String.class)))
                .thenReturn(Optional.of(user));


        //when
        ResponseStatusDto response = userService.login(request, servletResponse);
        //then
        assertThat(response.getStatusCode()).isEqualTo(StatusEnum.LOGIN_SUCCESS.getStatusCode());
        assertThat(response.getMsg()).isEqualTo(StatusEnum.LOGIN_SUCCESS.getMsg());
        assertThat(servletResponse.getHeaderValue("Authorization").toString()).isNotEmpty();

        verify(userRepository,times(1)).findByUsername(any(String.class));

    }
}

💡 에러3 . Please remove unnecessary stubbings or use 'lenient' strictness

1 발생

mockMvc를 이용한 userController 단 테스트 코드 작성 후 런 하였더니 생긴 에러


2 코드

Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
  1. -> at com.example.myblog1.user.controller.UserControllerTest.signup_failed_pw(UserControllerTest.java:105)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
org.mockito.exceptions.misusing.UnnecessaryStubbingException: 
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.

3 원인

Spring Boot에서 JUnit5와 이에 포함된 Mockito Core 3.x 버전을 사용할 때 아래와 같이 UnnecessaryStubbingException 에러가 나면서 테스트코드가 실패하는 경우가 있습니다.

  1. -> at com.example.myblog1.user.controller.UserControllerTest.signup_failed_pw(UserControllerTest.java:105)
Please remove unnecessary stubbings or use 'lenient' strictness.

이는 mockito-core버전이 1.x일 때 없었던 Strictness(테스트코드의 엄격성)을 규정하기 위해 생긴 에러이며, mockito-core 2.x 버전에서 도입되었습니다.

불필요한 스터빙을 하지 않도록 되어있는데, 현재 코드 105줄에 쓰이지 않는 스터빙을 해놨기 때문에 저런 메시지가 보이는 것이고, lenient는 그런 제약을 느슨하게 허용하게 해줍니다.


4 해결

lenient() 메서드를 앞에 추가하기

doReturn, when 등의 앞에 lenient()를 추가해서 해당 stubbing이 미사용될 수 있음을 표시합니다.

 lenient().when(userService.signup(request))
                .thenReturn(response);

lenient는 그런 제약을 느슨하게 허용하게 함으로 에러 해결함

💡 에러4 exceptions.misusing.InvalidUseOfMatchersException:

1 발생

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Misplaced or misused argument matcher detected here:

-> at com.team.final8teamproject.contact.service.FaqServiceImplTest.lambda$getFaqList_throw$1(FaqServiceImplTest.java:149)

You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
    when(mock.get(anyInt())).thenReturn(null);
    doThrow(new RuntimeException()).when(mock).someVoidMethod(any());
    verify(mock).someMethod(contains("foo"))

게시글이 없다면 예외처리 할 수 있는 테스트 코드를 짤때 발생함


2 코드

  @Test
  @DisplayName("FAQ 조회_글이 없을때 예외발생")
  void getFaqList_throw() {
    //given
    int page = 1;
    int size = 10;
    Direction direction = Direction.DESC;
    String properties = "createdDate";
    
    //lenient() Strictness(테스트코드의 엄격성) 느슨하게 해 줌
    lenient().when(faqRepository.findAll(PageRequest.of(page - 1, size, direction, properties)))
        .thenReturn(Page.empty());
    //when&then
    assertThrows(CustomException.class, () -> {
     //faqServiceImpl.getFaqList(page - 1, size, direction, properties);
      faqServiceImpl.getFaqList(any(),any(),any(),any());
    });
  }

3 원인

faqServiceImpl.getFaqList(any(),any(),any(),any());


4 해결

faqServiceImpl.getFaqList(any(),any(),any(),any());을

faqServiceImpl.getFaqList(page - 1, size, direction, properties);

해결

profile
개발자꿈나무

0개의 댓글