반응형
예제 코드
Mockito 종류
Mockito.mock(T)
@Mock
@MockBean
@Spy
@SpyBean
@InjectMocks
예제 코드
Entity
@Getter
@Setter
@NoArgsConstructor
public class Entity {
private Long id;
private String name;
@Builder
public Entity(Long id, String name) {
this.id = id;
this.name = name;
}
}
- 목적 : 저장 목적을 가지고 있는 객체
EintityIdCreator
@Component
public class EntityIdCreator {
private long id = 0;
public long generateNewId() {
long generateId = id;
id++;
return generateId;
}
}
- 목적 : Entity의 Id를 생성하기 위한 Component
TestRepository
@Repository
public class TestRepository {
private static final Map<Long, Entity> db = new HashMap<>();
public Entity save(Entity saveCandidate) {
db.put(saveCandidate.getId(), saveCandidate);
return saveCandidate;
}
public List<Entity> findAll() {
System.out.println("spy test");
Set<Map.Entry<Long, Entity>> entries = db.entrySet();
return entries.stream()
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}
public Entity findById(Long id) {
return db.get(id);
}
}
- 목적 : Entity 를 저장/조회 하는 것
TestService
@Service
@RequiredArgsConstructor
public class TestService {
private final TestRepository testRepository;
private final EntityIdCreator entityIdCreator;
public Long save(Entity entity) {
entity.setId(entityIdCreator.generateNewId());
return testRepository.save(entity).getId();
}
public List<Entity> findEntiy() {
return testRepository.findAll();
}
public Entity findById(Long id) {
return testRepository.findById(id);
}
}
- 목적 : 비즈니스 로직을 처리하는 방법
Test code 작성
- TestService를 비즈니스 로직 test
- repository, creator id 는 mock 객체로 사용 예정
1. Spring 없이 Mockito를 이용한 Test 코드 작성
1.1 Mockito 사용해서 test code 작성하기
Test code 설명
class MockitoTest {
TestService testService;
TestRepository testRepository;
EntityIdCreator entityIdCreator;
@BeforeEach
void setUp() { // mock 객체 생성
testRepository = Mockito.mock(TestRepository.class);
entityIdCreator = Mockito.mock(EntityIdCreator.class);
testService = new TestService(testRepository, entityIdCreator);
}
@Test
void createWithMockTest() throws Exception {
// given
Entity entity = Entity.builder()
.id(1L)
.name("name")
.build();
Mockito.doAnswer(invocation -> 1L)
.when(entityIdCreator).generateNewId();
Mockito.doAnswer(invocation -> entity)
.when(testRepository).save(entity);
// when
Long saveId = testService.save(entity);
// then
assertEquals(1L, saveId);
Mockito.verify(testRepository, Mockito.times(1)).save(entity);
Mockito.verify(entityIdCreator, Mockito.times(1)).generateNewId();
}
}
- setUp
- mock 객체를 직접 생성해서 주입을 해주는 역할
- Mockito.mock 을 이용해 mock 객체 생성
- mock객체가 어떤 메서드를 호출했을때 기대 되는 반환 값을 설정 할 수 있습니다.
Mockito.doAnswer(invocation -> 1L)
.when(entityIdCreator).generateNewId();
- doAnswer를 통해 반환 값을 설정
- when 을 통해 어떤 객체인지 설정
- 함수 이름을 사용해서 함수를 호출 하는것 처럼 설정합니다.
Mockito.verify
일 경우 함수가 몇 번 호출 되었는지 나타냅니다.Mockito.verify(testRepository, Mockito.times(1)).save(entity);
를 해석하면testRepository
에 있는save
라는 함수는Mockito.times(1)
를 통해 1번만 호출되어있다는 것을 확인할 수 있다는 의미 입니다.
Stub이 생성되지 않는 경우
- testRepository는 save, findAll, findById 총 3가지 method 로 구성, 하지만 save만 mock stub으로 설정했습니다. 그러므로 finAll, findById는 Stub으로 설정이 되어있지 않아 test code에서 호출하면 타입별로 default 값이 반환 됩니다.
- default 값 :
Answers.RETURNS_DEFAULTS
에 설정이 되어있습니다.
1.2 @Mock 방식
Mockito.mock()
으로 객체 생성한 것과 동일한 결과를 반환 합니다.- @Mock
@ExtendWith(MockitoExtension.class)
을 사용하고 싶을 경우MockitoExtension
class를 import 해와야 합니다. - 아래 예제에서는 class위에
@ExtendWith(MockitoExtension.class)
설정
Test code 작성
@ExtendWith(MockitoExtension.class)
class MockAnnotationTest {
@InjectMocks
TestService testService;
@Mock
TestRepository testRepository;
@Mock
EntityIdCreator entityIdCreator;
@Test
void mockInjectionTest() throws Exception {
// given
Entity entity = Entity.builder()
.id(1L)
.name("name")
.build();
Mockito.doAnswer(invocation -> 1L)
.when(entityIdCreator).generateNewId();
Mockito.doAnswer(invocation -> entity)
.when(testRepository).save(entity);
// when
Long saveId = testService.save(entity);
// then
assertEquals(1L, saveId);
Mockito.verify(testRepository, Mockito.times(1)).save(entity);
Mockito.verify(entityIdCreator, Mockito.times(1)).generateNewId();
}
}
@InjectMocks
- @Mock으로 생성한 객체를 MockInjection을 통해 주입을 해줍니다.
1.3 @Spy
Spy 정의
- Spy일 경우 doAnswer을 통해 stub을 설정하지 않을 경우 원래 기본 비즈니스 로직을 실행합니다.
Test code 작성
@ExtendWith(MockitoExtension.class)
class SpyTest {
@InjectMocks
TestService testService;
@Spy
TestRepository testRepository;
@Spy
EntityIdCreator entityIdCreator;
@Test
void mockInjectionTest() throws Exception {
// given
Entity entity = Entity.builder()
.id(1L)
.name("name")
.build();
Mockito.doAnswer(invocation -> 1L)
.when(entityIdCreator).generateNewId();
Mockito.doAnswer(invocation -> Collections.emptyList())
.when(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();
Mockito.verify(entityIdCreator, Mockito.times(1)).generateNewId();
}
}
- 현재 code에서도
@IndectMocks
를 통해@Spy
로 생성된 객체를 주입 받고 있습니다. - 현재 예시에서는
findById
와findEntiy
는 stub을 설정하지 않았습니다. 그렇기 때문에 원래testRepository
의 함수를 호출 하게 됩니다.
2. SpringBoot를 이용해서 Mockito 사용하기
종류
@MockBean
@SpyBean
2.1 @MockBean
Test code 작성
@SpringBootTest
public class MockBeanTest {
@Autowired
TestService testService;
@MockBean
TestRepository testRepository;
@MockBean
EntityIdCreator entityIdCreator;
@Test
void mockInjectionTest() throws Exception {
// given
Entity entity = Entity.builder()
.id(1L)
.name("name")
.build();
willReturn(1L)
.given(entityIdCreator).generateNewId();
willReturn(entity)
.given(testRepository).save(entity);
// when
Long saveId = testService.save(entity);
// then
assertEquals(1L, saveId);
Mockito.verify(testRepository, Mockito.times(1)).save(entity);
Mockito.verify(entityIdCreator, Mockito.times(1)).generateNewId();
}
}
MockBean
은 해당 annotation이 붙은 객체를 mock 객체로 생성하고 Bean으로 등록합니다.MockBean
은 Mockito라는 package에 있지 않고 Spring package에 존재합니다. 그렇기 때문에 Spring 영역 dependency라는 것을 알 수 있습니다.@SpringBootTest
를 통해 Mock Bean객체는 @Autowired로 주입 받을 수 있습니다.- willReturn 이라는 함수를 통해 반환 값을 받을 수있습니다
- given을 통해 어떤 객체의 stub을 설정할지 지정하고, 뒤에 메서드를 호출했기 때문에 해당 method가 호출 되면 willReturn에 지정된 값이 반환된다는 뜻입니다.
2.2 @SpyBean
@SpringBootTest
public class SpyBeanTest {
@Autowired
TestService testService;
@SpyBean
TestRepository testRepository;
@SpyBean
EntityIdCreator entityIdCreator;
@Test
void spyBeanTest() throws Exception {
// given
Entity entity = Entity.builder()
.id(1L)
.name("name")
.build();
willReturn(1L)
.given(entityIdCreator).generateNewId();
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();
Mockito.verify(entityIdCreator, Mockito.times(1)).generateNewId();
}
}
- 위에서 언급한 Spy와 동일합니다. 하지만 Bean 객체로 등록이 되기 때문에 @Autowired를 붙인 객체에 자동으로 주입이 되는 형태를 가집니다.
- 유의점 :
@SpyBean
을 interface에 붙일 경우 interface의 구현체를 spring context 에 등록이 되어있어야 합니다. - -> MockBean인 경우 전체가 stub을 적어도 Default 값을 넣어서 stub을 형성하지만, Spy는 stub이 없는 경우 자동으로 구현된 메서드를 호출 하는 형태이기 때문입니다
반응형
'Spring > 개념' 카테고리의 다른 글
Logging (0) | 2021.12.17 |
---|---|
Servlet과 Spring Container (1) | 2021.12.04 |
Spring Boot로 MySQL replication 연동하기 (0) | 2021.10.06 |
04 Exception (0) | 2021.05.19 |
03 템플릿 (0) | 2021.05.08 |