복잡한 로직의 코드를 작성할때, 리팩토링을 해야할때 잘 짜여진(경우의 수를 반영한) 테스트 코드가 있으면 코드의 결과에 대한 확신이 생깁니다.
이때 Mocking을 활용하면 테스트 코드를 더 원활하게 작성할 수 있습니다. Mocking이란 단위 테스트를 작성할때 데이터베이스와 같이 외부에 의존하지 않고, 실행될 함수를 테스트에 직접 정의하여 대체하는 기법입니다.
1. 의존성 주입
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
testCompile group: 'junit', name: 'junit', version: '4.12'
testImplementation 'org.mockito:mockito-core:3.6.0'
testImplementation 'org.easytesting:fest-assert:1.4'
}
mockito를 사용했습니다. fest-assert는 테스트 검증을 쉽게 할 수 있도록 도와줍니다. (필수 아님). lombok나 string-boot는 편의를 위해 사용했습니다.
2. Book관련 클래스 정의
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
@AllArgsConstructor
@Getter
@Builder
@ToString
public class Book {
private Integer id;
private String title;
}
데이터 클래스 입니다.
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
@Repository
@Slf4j
public class BookRepository {
public Integer getBookLocation(int id) {
return id % 2;
}
public void save(Book book) {
log.debug("save book === {}", book);
}
public void sell(int id) {
log.debug("sell id === {}", id);
}
}
데이터베이스에 접근한다고 가정하고 보시면 됩니다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookService {
@Autowired
private BookRepository bookRepository;
public Integer getBookLocation(int id) {
return bookRepository.getBookLocation(id);
}
public void save(Book book) {
bookRepository.save(book);
}
public void sell(int id) {
if (id > 100) {
throw new RuntimeException("no sell");
}
bookRepository.sell(id);
}
}
repository를 활용하는 비지니스 클래스입니다. 테스트를 위해 작성한 코드이니 큰 의미는 없습니다.
3. 테스트 코드 작성
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class MockTest {
@InjectMocks
private BookService bookService;
@Mock
private BookRepository bookRepository;
@Test
public void given_예제() {
// given
int id = 1;
int expected = 1;
given(bookRepository.getBookLocation(id)).willReturn(expected);
// when
int result = bookService.getBookLocation(id);
// then
assertThat(result).isEqualTo(expected);
}
@Test
public void 함수반환값이없는경우_doNothing() {
// given
Book book = Book.builder().id(1).title("제목").build();
doNothing().when(bookRepository).save(book);
// when
bookService.save(book);
// then
verify(bookRepository, times(1)).save(book);
}
@Test(expected = RuntimeException.class)
public void 함수결과가_예외인경우() {
// given
int id = 1000;
// when
bookService.sell(id);
// then
verify(bookRepository, times(0)).sell(id);
// exception
}
@Test
public void 정상테스트() {
// given
int id = 1;
doNothing().when(bookRepository).sell(id);
// when
bookService.sell(id);
// then
verify(bookRepository, times(1)).sell(id);
}
}
테스트에 필요한 패키지 import static 정의
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;
given-when-then 작성
테스트 함수에 given-when-then을 먼저 작성하여 형식을 지키도록 합니다.
given
given은 함수 호출하고 결과를 반환하는 경우 사용
doNothing
doNothing은 함수 호출의 결과가 없는 경우 사용
assertThat
assertThat은 when에서 결과를 비교할 때 사용
verify
mocking된 클래스가 얼마나 호출 되었는지 확인
댓글
댓글 쓰기