테스팅(Testing) - Mockito

박채은·2023년 1월 10일
0

Spring

목록 보기
24/35

Mock

간단하게 말하면 가짜 혹은 진짜인 것처럼 보이는 물건이라고 말할 수 있다.

테스트에서 Mock이란 가짜 객체를 말하고, Mocking이란 Mock 객체를 사용해서 진짜처럼 보이게 하는 것을 말한다.

언제 사용하나요?

슬라이스 테스트에서 사용한다!

보통 한 계층은 다른 계층들과 연동되어 있는데 슬라이스 테스트는 해당 계층에 대해서만 테스트를 진행하는 것이므로 다른 계층과의 연동을 끊어줘야 한다.
이때 연동을 끊어주는 역할을 Mock 객체가 한다.


Mockito

  • Spring Framework 자체적으로도 지원하고 있는 Mocking 라이브러리
    (Mocking 할 수 있게 해주는 오픈 소스 라이브러리)

애너테이션

  • @SpringBootTest + @AutoConfigureMockMvc : Spring 애플리케이션에서 사용하는 빈을 불러와서 Application Context에 등록해준다.

  • @MockBean: Application Context에 등록되어 있는 Bean에 대한 Mock 객체를 생성하고 필드에 주입해주는 역할을 한다.

    • Mock 객체 생성과 DI(주입)이 한번에 일어난다! (@Mock + @InjectMocks)
  • @Autowired: Application Context에 등록되어 있는 Bean을 가져와서 사용한다. (Bean을 그대로 가져온 것! Mock 객체가 아니다!)

  • @ExtendWith(MockitoExtension.class): Spring을 사용하지 않고, Junit에서 Mockito의 기능을 사용할 수 있게 해준다.
    (Application Context는 생성되지 않음!)
    Spring을 사용하지 않고 = 스프링 환경 위에서 테스트 하지 않겠다.

  • @Mock: 해당 필드의 객체를 Mock 객체로 생성

  • @InjectMocks: 해당 필드에 @Mock을 통해 생성한 Mock 객체를 주입해준다.


Controller 슬라이스 테스트

@SpringBootTest
@AutoConfigureMockMvc
class MemberControllerMockTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private MemberService memberService;

    @MockBean
    private MemberMapper mapper;
  • mockMvc는 Controller를 사용하기 위해서 @Autowired로 주입받는다.

  • @SpringBootTest를 작성했기 때문에 @MockBean 애너테이션을 사용해서 memberService를 Mock 객체로 만든다.

  • mapper는 @MockBean이던 @Autowired이던 상관없다.
    → 하지만 되도록이면 @MockBean을 사용하자!

💡 왜 mapper는 @MockBean이던 @Autowired이던 상관없나요?

@MockBean로 하면, MemberMapper를 Mock 객체로 만들어서 항상 Stub 데이터를 리턴하도록 하므로 시간이 적게 걸린다.
그에 비해 @Autowired로 하면 실제 mapper를 통해서 실행시키는 것이므로 시간이 오래 걸린다.
(mapper에서 DB에 접근해서 데이터를 가져오기도 하기 때문에 시간이 많이 걸릴 수 밖에 없다.)

@MockBean@Autowired은 성능 면에 차이가 있다.
→ Mock 객체로 만들면 시간이 적게 걸리고, Autowired로 주입 받으면 시간이 오래걸린다.


Service 계층 슬라이스 테스트

@ExtendWith(MockitoExtension.class)
public class MemberServiceMockTest {
    @Mock
    private MemberRepository memberRepository;

    @InjectMocks
    private MemberService memberService;
  • @ExtendWith(MockitoExtension.class)를 추가해서 Spring을 사용하지 않고 Mockito를 사용하도록 한다.
  • Service 계층을 테스트해야 하므로, MemberService를 생성해야 한다.
  • Service 계층은 memberRepository와 의존 관계에 있고memberRepository는 테스트하는 계층이 아니므로 @Mock을 통해 Mock 객체로 만들어준 다음에 @InjectMocks를 통해 MemberService에 주입해줘야 한다.

💡 Service 계층에서 @SpringBootTest 애너테이션을 안 붙인 이유는?
-> 딱히 Spring에 의존적인 것이 없기 때문에!
Spring Bean들을 많이 사용해야 한다면 붙이는 것이 좋을 것 같다.


given()

  • Mockito에서 지원하는 Stubbing 메서드
  • Mockito에서 지원하는 when()과 동일한 기능을 한다.

given(Mock 객체의 메서드 호출).willReturn(Stub 데이터)

💡 Stubbing?
테스트를 위해서 Mock 객체가 항상 일정한 Stub 데이터를 리턴하도록 하는 것


🚨 내가 실수했던 부분 - 애너테이션

@MockBean@Autowired@SpringBootTest + @AutoConfigureMockMvc처럼 Application Context를 생성해줄 수 있는 애너테이션이 있어야 사용할 수 있다!

상황

  • OrderService의 슬라이스 테스트 코드를 작성하고 있었다.
  • orderService의 cancelOrder에 대한 테스트를 하고자 필드로 OrderService orderService를 @Autowired로 주입받으려고 했다.

문제점

"OrderService 클래스에는 @Service 애너테이션이 붙어 있고, Bean에 등록되었으니 당연히 @Autowired로 주입받을 수 있는 거 아닌가?" 라고 생각했는데 아니였다!
실제 Spring 애플리케이션에서의 Application context에는 Bean으로 등록되어 있으나 Test에서는 아니다.
(나는 Test도 Spring 애플리케이션의 일부라 같이 Application context를 공유하는지 알았음)

@Test이 붙은 테스트 클래스는 실제 애플리케이션이 실행되는 것이 아니라 단순히 테스트를 실행하는 것이기 때문에 Application Context가 만들어지지 않는다.
만약에 Test 클래스에 @Autowired를 이용해서 DI를 받고 싶다면 DI로 주입 받을 객체가 있는 Application Context가 있어야 한다.

가장 쉬운 방법은 @SpringBootTest 애너테이션을 추가하는 것인데 이건 클래스 하나를 테스트 하기위해 애플리케이션 전체를 실행시키는 것과 동일한 비용이 드는 작업이라서 권장되지 않는 방법이다.


각 계층에서 Application Context를 생성해주는 애너테이션은 무엇일까?

Q1) @SpringBootTest을 Controller가 아닌 다른 계층에 사용해도 되는 걸까?
➡️ 사용해도 된다!

Q2) @WebMvcTest은 Controller에 관련된 Bean들만 가져오는데, 다른 계층인 Service와 데이터 액세스 계층은 어떻게 Bean들을 가져오는 것일까?

위와 같은 궁금증이 생겨서 구글링을 해보았다.

Controller

  • @SpringBootTest + @AutoConfigureMockMvc : 전체 Application Context를 가져온다.

  • @WebMvcTest : request나 response를 다루는 web layer 부분의 Application Context만 가져온다.
    즉, @Controller@RestController가 붙은 클래스의 핸들러 메서드를 테스트할 때 사용한다.
    (@Service, @Component, @Repository 등은 불가)

@WebMvcTest와 @AutoConfigureMockMvc의 차이점
@WebMvcTest에 대한 자세한 설명

Service

서비스 계층은 Mocking을 사용하는 경우가 아니라면, configuration에 대해 독립적인 비지니스 로직이므로 어떠한 애너테이션도 붙이지 않고 테스트하는 것이 이상적이다.

➡️ 굳이 Spring에 의존하지 않고 직접 Service 객체를 new로 생성해서 사용하는 것이 오히려 테스트 비용을 줄일 수 있다.

데이터 액세스 계층

  • @DataJpaTest: 오직 JPA의 configuration만 가져온다.(JPA 테스트와 연관된 config만 적용한다.)

    • 기본적으로 인메모리인 H2를 사용한다.
    • @Component이 붙은 클래스를 Application Context로 로드 하지 않는다.(@ComponentScan이 없다.)
  • @SpringBootTest + Memory DB 연결


실무에서는 통합 테스트가 아닌 한 @SpringBootTest를 거의 사용하지 않는다. 직접 new 객체로 생성해서 쓰거나 다른 애너테이션을 통해서 Application context를 구성하는 것이 좋은 방법인 것 같다.

[참고]
https://stackoverflow.com/questions/59097035/springboottest-vs-webmvctest-datajpatest-service-unit-tests-what-is-the-b
@SpringBootTest vs @DataJpaTest


주의할 점

1. @SpringBootTest와 @WebMvcTest는 같이 사용될 수 없다.
각자 MockMvc를 모킹하기 때문에 충돌이 발생한다!


[참고]

https://velog.io/@lxxjn0/Mockito%EC%99%80-BDDMockito%EB%8A%94-%EB%AD%90%EA%B0%80-%EB%8B%A4%EB%A5%BC%EA%B9%8C
40. 테스트
https://cornswrold.tistory.com/479
(-> 이 분은 나와 비슷한 에러를 겪으신 것 같다.)

[나중에 읽어볼 블로그]
블로그에서 설명하신 부분을 이해하고 싶은데, 아직 지식이 부족해서 이해가 안된다....ㅎ

https://cobbybb.tistory.com/23

0개의 댓글