모르지 않다는 것은 아는것과 다르다.

Junit

Junit5 애노테이션 비교

채마스 2022. 2. 27. 01:40

개요

  • 평소 착각할 수 있는 junit 애노테이션을 비교해 보고자 한다.



@Mock vs @MockBean

  • 공통점
    • 둘 모두 특정부분을 단위테스트 하고 싶을때, 테스트하고 싶은 모듈에 의존성을 가지고 있는 모듈을 Mock객체로 대체하고 조작하여 테스트할 수 있다.
    • 둘 모두 given, when. verify 등을 사용하여 행위를 테스트한다.
  • @Mock
    • 모키토 라이브러 내에 존재한다.
    • Mock은 가짜객체를 만드는데 스프링빈에 등록이 안되는 객체이다.
    • 스프링 컨테이너가 DI를 하는 방식이 아니라 객체생성시 생성자에 Mock객체를 직접 주입해준다.
    • 생성자 주입을 사용해야 편하게 사용 가능합니다.
    • 스프링을 띄우지 않으므로 MockBean을 사용할때보다 빠르다.
    • @InjectMocks에 대해서만 해당 클래스안에서 정의된 객체를 찾아서 의존성을 해결한다.
    • mocking 하지 않은 클래스는 절대 의존성이 주입되지 않는 것을 명심해야된다.
      • @Autowired 로 주입받은 빈은 관련된 의존성이 자동으로 주입되는 것과 차이가 있다.
    • 코드예시
      @ExtendWith(MockitoExtension.class) 
      class ServiceTest { 
    
          private Service service; 
    
          @Mock 
          private Repository Repository; 
    
          @BeforeEach 
          void setUp() { 
              service = new Service(repository); 
          } 
    
          @Test 
          void findAll() { 
              given(repository.findAll()).willReturn(...); 
              List<String> actual = service.findAll();
          }
  • @MockBean
    • 스프링부트 테스트 패키지 내에 존재한다.
    • MockBean은 가짜 Bean을 스프링에 등록해달라는 의미이다.
    • 스프링 컨테이너가 기존에 갖고있는 Bean객체는 MockBean객체로 치환되어 DI에 사용된다.
    • mock 객체를 스프링 컨텍스트에 등록하는 것이기 때문에 @SpringBootTest를 통해서 Autowired에 의존성이 주입되게 된다.
      @WebMvcTest(controllers = Controller.class) 
      @AutoConfigureMockMvc class ContollerTest { 
          private MockMvc mockMvc; 
    
          @MockBean 
          private Service service; 
    
          @BeforeEach 
          void setUp(WebApplicationContext webApplicationContext) { 
              this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
                  .addFilter(new CharacterEncodingFilter("UTF-8", true)) 
                  .build(); 
          } 
    
          @Test void findAll() { 
              given(service.findAll()).willReturn(...); 
              mockMvc.perform(get("/") 
                  .accept(MediaType.APPLICATION_JSON)) 
                  .andExpect(status().isOk())) ... 
          } 
      }
    • @WebMvcTest 이기 때문에 Controller까지는 로드 된다. -> 하지만 Service 는 가져오지 못한다. -> 그렇기 때문에 @MockBean 을 이용해서 service 를 가져올 수 있다.
    • @Mock 으로 가져오면 안될까? -> service 가 실제로 기능이 수행돼야 하기때문에 Bean Container 에 있는 service 가 필요하다.
      • @Mock 으로 만든다면 정상적인 기능 을 수행할 수 없다.
  • 정리하면 아래와 같다.

  • 정리하면 Bean이 Container에 의존할 경우에는 MockBean 을 사용하고, 그렇지 않을 경우에는 Mock을 사용한다.



@MockBean vs @SpyBean

  • @MockBean
    • MockBean은 해당 annotation이 붙은 객체를 mock 객체로 생성하고 Bean으로 등록한다.
    • @MockBean으로 선언한 객체와 같은 이름과 타입으로 이미 빈으로 등록되어있다면 해당 빈은 선언한 Mock 빈으로 대체된다.
    • @SpringBootTest를 통해 Mock Bean객체는 @Autowired로 주입 받을 수 있습니다.
    • willReturn 이라는 함수를 통해 반환 값을 받을 수 있다.
    • given을 통해 어떤 객체의 stub을 설정할지 지정하고, 뒤에 메서드를 호출했기 때문에 해당 method가 호출 되면 willReturn에 지정된 값이 반환된다는 뜻이다.
    • 예시는 아래와 같다.
      @SpringBootTest
      public class MockBeanTest {
          @Autowired
          TestService testService;
    
          @MockBean
          TestRepository testRepository;
    
          @Test
          void mockInjectionTest() throws Exception {
    
              // given
              Entity entity = Entity.builder()
                      .id(1L)
                      .name("name")
                      .build();
    
              willReturn(entity)
                      .given(testRepository).save(entity);
    
              // when
              Long saveId = testService.save(entity);
    
              // then
              assertEquals(1L, saveId);
              Mockito.verify(testRepository, Mockito.times(1)).save(entity);
          }
      }
  • @SpyBean
    • @MockBean과의 차이점은 @SpyBean을 interface에 붙일 경우 interface의 구현체를 spring context 에 등록이 되어있어야 한다.
      • MockBean인 경우 전체가 stub을 적어도 Default 값을 넣어서 stub을 형성하지만, Spy는 stub이 없는 경우 자동으로 구현된 메서드를 호출 하는 형태이기 때문이다.
    • 예시는 아래와 같다.
      @SpringBootTest
      public class SpyBeanTest {
          @Autowired
          TestService testService;
    
          @SpyBean
          TestRepository testRepository;
    
          @Test
          void spyBeanTest() throws Exception {
    
              // given
              Entity entity = Entity.builder()
                      .id(1L)
                      .name("name")
                      .build();
    
              willReturn(Collections.emptyList())
                      .given(testRepository).findAll();
    
              // when
              Long saveId = testService.save(entity);
              Entity findEntity = testService.findById(1L);
              List<Entity> list = testService.findEntiy();
    
    
        // then
        assertEquals(0, list.size());
        assertEquals(1L, saveId);
        assertEquals(1L, findEntity.getId());
        Mockito.verify(testRepository, Mockito.times(1)).findAll();
    }
}
<br><br><br>

# @ExtendWith(SpringExtension.class) vs @ExtendWith(MockitoExtension.class)
- @ExtendWith(SpringExtension.class)
  - SpringContainer를 로드하므로 Test 객체에 @Autowired를 통해 Bean 의존성을 주입시킬 수 있다.
  - 또한 Bean을 Mocking하기위한 @MockBean 기능을 사용할 수 있다.
  - 테스트를 위해 Spring이 필요하다면 위 코드가 필요하다.

- @ExtendWith(MockitoExtension.class)
  - SpringContainer를 로드하지않고 테스트를 위한 기능만 제공한다.
  - @Mock, @Spy 기능을 사용할 수 있다.
  - 테스트에 Spring이 필요없이 순수한 단위 테스트만 필요하다면 위 코드를 추가하면 된다.



<br><br><br>

## REFERENCES
- https://frozenpond.tistory.com/83
- https://beomseok95.tistory.com/303
- https://cobbybb.tistory.com/16
- https://dundung.tistory.com/230
- https://clack2933.tistory.com/m/28

'Junit' 카테고리의 다른 글

MockMvc  (0) 2022.02.27
Mockito  (0) 2022.02.27
# Junit 5 기본 애노테이션  (0) 2022.02.27
Controller 단위테스트  (0) 2022.02.27
Bad Request 처리하기  (0) 2022.02.27