개요
평소 착각할 수 있는 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