요즘 Unit Test에 대한 관심이 많아 졌으나 개인적으로 실제 대충 사용하여 잘 모르는 부분입니다.
특히 모듈로 분리하였을 경우 Test가 힘든 점이 있어 Test의 관련된 내용을 조금 정리하고자 합니다.
그 중 Spring Boot의 @Autowired와 Mockito의 @InjectMocks을 Spring Boot Tests에 의존성을 주입하여 사용하는 방법을 간단하게 테스트 진행할 것입니다.
테스트 주석의 이해
Mockito의 가장 일반적으로 사용되는 @Mock주석은테스트를 위한 종속성의 모의 인스턴스를 생성합니다. 이는 종종 @Mock으로 표시된 모의를 테스트 중인 대상 객체에 주입하는 @InjectMocks와 함께 사용되곤 합니다.
Spring Boot의 주석 @MockBean은 모의된 Spring 빈을 만드는데 도움이 도리수 있습니다. 모의된 빈은 컨텍스트의 다른 빈에서 사용할 수 있습니다. 게다가 Spring 컨텍스트가 Mocking없이 사용할 수 있는 빈을 스스로 만드는 경우 @Autowired 주석을 사용하여 주입할 수 있습니다.
예제 설정
1. 의존성(Dependencies)
Maven 의존성 설정
<!-- spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.2.5</version>
<scope>test</scope>
</dependency>
<!-- mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
</dependency>
Gradle 의존성 설정
dependencies {
// spring-boot
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// mokito
implementation 'org.mockito:mockito-core:5.11.0'
}
2. DTO
서비스에서 사용할 DTO를 생성합니다.
public class Book {
private String id;
private String name;
private String author;
public Book(String id, String name, String author) {
this.id = id;
this.name = name;
this.author = author;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
3. Service
DB(DataBase)을 담당하는 서비스를 정의 합니다.
단순한 테스트 이므로 실제 DB와 상관이 없이 사용한느 것처럼 유사하게 구성합니다.
@Service
public class DatabaseService {
public Book findById(String id) {
// querying a Database and getting a book
return new Book("id","Name", "Author");
}
}
위 Service(DatabaseServcie)를 의존하는 서비스를 생성합니다.
@Service
public class BookService {
private final DatabaseService databaseService;
private final ObjectMapper objectMapper;
public BookService(DatabaseService databaseService, ObjectMapper objectMapper) {
this.databaseService = databaseService;
this.objectMapper = objectMapper;
}
String getBook(String id) throws JsonProcessingException {
Book book = databaseService.findById(id);
return objectMapper.writeValueAsString(book);
}
}
getBook()이란 매서드는 DatabaseServcie를 사용하여 데이터베이스에서 Book을 가져와서 Jackson의 ObjectMapper를 활용하여 JSON 문자열로 변환하여 반환합니다.
즉, BookService는 DatabaseService와 ObjectMapper 2가지의 의존성을 가지고 있습니다.
테스트
1. @Mock 및 @InjectMocks 사용
@Mock을 사용하여 DatabaseService와 ObjectMapper 두 의존성을 모의(가상)하고, @InjectMocks를 사용하여 BookService에 주입합니다.
@ExtendWith(MockitoExtension.class)
class BookServiceMockAndInjectMocksUnitTest {
@Mock
private DatabaseService databaseService;
@Mock
private ObjectMapper objectMapper;
@InjectMocks
private BookService bookService;
@Test
void givenBookService_whenGettingBook_thenBookIsCorrect() throws JsonProcessingException {
Book book1 = new Book("1234", "Inferno", "Dan Brown");
when(databaseService.findById(eq("1234"))).thenReturn(book1);
when(objectMapper.writeValueAsString(any())).thenReturn(new ObjectMapper().writeValueAsString(book1));
String bookString1 = bookService.getBook("1234");
Assertions.assertTrue(bookString1.contains("Dan Brown"));
System.out.println("finish.");
}
}
@ExtendWith(MockitoExtendsion.class) 애너테이션으로 테스트에서 객체를 모의하고 주입할 수 있습니다.
@Mock 애너테이션을 활용하여 DatabaseService 및 ObjectMapper를 모의 객체로 생성합니다.
@InjectMocks 애터네이션을 BookService에 사용하면 @Mocks로 선언되 모든 종속성이 BookService에 주입이 될 것입니다.
마지막으로 BookService의 getBook()를 테스트 합니다
주의할 것은 테스트할 서비스의 모든 종속성을 모의(Mock)하는 것이 필수입니다. 만약 하나라도 모의(Mock)하지 않았다면 NullPointerException이 발생할 것입니다.
2. @Autowired를 @MockBean과 함께 사용
@Mock 및 @InjectMocks을 사용한 예제(1번 예제)의 경우 두 종속성을 모의하였습니다. 하지만 일부 종속성을 모의하고 다른 종속성은 모의하지 않아야 할 수도 있습니다. ObjectMapper의 동작을 모의할 필요가 없고 DatabaseService만 모의 한다고 가정할 경우 테스트에서 Srping Context를 로드하고 있으므로 @Autowired와 @MockBean 주석을 조합하여 사용할 수 있습니다.
@SpringBootTest
public class BookServiceAutowiredAndMockBeanUnitTest {
@MockBean
private DatabaseService databaseService;
@Autowired
private BookService bookService;
@Test
void givenBookService_whenGettingBook_thenBookIsCorrect() throws JsonProcessingException {
Book book1 = new Book("1234", "Inferno", "Dan Brown");
when(databaseService.findById(eq("1234"))).thenReturn(book1);
String bookString1 = bookService.getBook("1234");
Assertions.assertTrue(bookString1.contains("Dan Brown"));
System.out.println("finish.");
}
}
@SpringBootTest 애너테이션으로 Srping Context에서 빈을 사용하게 됩니다.
DatabaseServcie에 @MockBean 애너테이션을 사용하고 BookService에 @Autowired를 사용하여 인스턴스를 가져옵니다.
BookService 빈이 주입 되면 실제 DatabaseService 빈은 모의된 빈으로 대체됩니다.
ObjectMapper는 기존의 생성한 것과 동일하게 유지됩니다.
이제는 ObjectMapper에 대한 어떤 동작도 모의할 필요가 없습니다. 이 방법은 중첩된 빈의 동작을 테스트해야 하지만 모든 종속성을 모의하고 싶지 않을 때 유용합니다.
3. @Autowired와 @InjectMocks를 함게 사용
@MockBean 대신 @InjectMocks를 사용할 수도 있습니다.
@SpringBootTest
public class BookServiceAutowiredAndInjectMocksUnitTest {
@Mock
private DatabaseService databaseService;
@Autowired
@InjectMocks
private BookService bookService;
@Test
void givenBookService_whenGettingBook_thenBookIsCorrect() throws JsonProcessingException {
Book book1 = new Book("1234", "Inferno", "Dan Brown");
MockitoAnnotations.openMocks(this);
when(databaseService.findById(eq("1234"))).thenReturn(book1);
String bookString1 = bookService.getBook("1234");
Assertions.assertTrue(bookString1.contains("Dan Brown"));
}
}
@MockBean 대신 @Mock을 사용하여 DatabaseService를 모의합니다. @Autowired외에도 BookService인스턴스에 @InjectMock 애너테이션을 추가합니다.
두 가지 애너테이션을 같이 사용하면 @InjectMocks은 모의된 종속성을 자동으로 주입하지 않고 자동 연결된 BookService 객체는 테스트가 시작될 때 주입됩니다.
하지만 나중에 테스트에서 MockitoAnnotations.openMocks() 매서드를 호출하여 DatabaseService의 모의 인스턴스를 주입할 수 있습니다. 이 매서든느 @InjectMocks로 표시된 필드를 찾아 모의 객체를 주입합니다.
DatabaseeService의 동작을 모의하기 직전에 테스트에서 호출합니다. 이 메서드는 모의를 사용할 때와 종ㅅ혹성에 실제 빈을 사용할 대를 동적으로 결장하고자 할 때 유용합니다.
접근 방식 비교
접근방법 | 설명 | 사용법 |
@Mock과 @InjectMocks |
Mockito의 @Mock 주석을 사용하여 종속성의 모의 인스턴스를 생성하고 @InjectMocks를 사용하여 이러한 모의 인스턴스를 테스트 중인 대상 객체에 주입합니다. | 테스트 중인 클래스의 모든 종속성을 모의하려는 단위 테스트에 적합합니다. |
@Autowired를 사용한 @MockBean |
Spring Boot의 @MockBean 어노테이션을 활용하여 모의 Spring 빈을 생성하고 @Autowired를 사용 하여 이러한 빈을 주입합니다. | Spring Boot 애플리케이션에서 통합 테스트에 이상적입니다. Spring의 종속성 주입에서 다른 빈을 가져오는 동안 일부 Spring 빈을 모의할 수 있습니다. |
@InjectMocks와 @Autowired |
Mockito의 @Mock 어노테이션을 사용하여 모의 인스턴스를 생성하고, @InjectMocks를 사용 하여 이러한 모의 인스턴스를 Spring을 사용하여 이미 자동으로 연결된 대상 빈에 주입합니다. | Mockito를 사용하여 주입된 Spring Bean을 재정의하기 위해 일부 종속성을 일시적으로 모의해야 하는 시나리오에서 유연성을 제공합니다. Spring 애플리케이션에서 복잡한 시나리오를 테스트하는 데 유용합니다. |
Mock을 잘 사용하면 멀티 모듈에서도 단위 테스트에 있어서 보다 효율적인 테스트가 가능할 것으로 보입니다.
적재적소에 사용한다면 분명히 효율적인 테스트를 함으로써 Test로 인한 생산성 저하를 막을 수 있지 않을까 싶습니다.
!! 만약 잘못된 부분이나 수정되어야 할 부분이 있으면 댓글로 말씀해 주세요 !!
참고: https://www.baeldung.com/spring-test-autowired-injectmocks
'Development > Java' 카테고리의 다른 글
[JAVA] 효과적인 Logging 14가지 가이드 (0) | 2024.08.01 |
---|---|
[JAVA] 2차원 배열에서 최대값, 최소값 구하기(for, stream api) (0) | 2024.07.31 |
[JAVA] 소수(Decimal)을 분수(Fraction)로 표현하기 (2) | 2024.07.24 |
[JAVA] Stream API에서 NoSuchElementException을 방지하는 방법 (3) | 2024.07.22 |
JAVA에서 IPv4 사용하기 (0) | 2019.07.24 |