Service 단위테스트
- Service 레이어만 단위테스트 하기위해서는 먼저 Controller와의 연결을 끊어야한다.
- Controller는 Web모듈이므로 Service Test를 진행하려면 Web에 대한 의존성을 받으면 안된다.
- @WebMvcTest, @SpringBootTest와 같은 테스트를 사용하면 Service만을 테스트하기가 어려워진다.
- 두 번째로 Repository와의 연결을 끊어야 한다.
- Domain을 통해 비즈니스 로직은 수행해야하지만, 실제로 DB에 저장할 건 아니기 때문에 이 부분을 제거할 방법이 필요하다.
- SpringBoot 테스트는 특정 객체를 가짜로 대체할 Mocking을 제공하고 있고, 아래와 같은 Annotation을 제공한다.
- @Mock
- @MockBean
- @Spy
- @SpyBean
- @ExtendWith(MockitoExtension.class) 을 추가하여 단위 테스트를 작성한다.
- Service 내에 의존하고 있는 Repository를
@Mock으로 선언하면 Repository Bean에 의존하지 않고 테스트가 가능
해진다.
- Service 클래스를 @InjectMocks로 선언함으로써,
@Mock으로 선언된 가짜 객체들을 의존한 Service 객체가 생성
된다.
- 예시는 아래와 같다.
@ExtendWith(MockitoExtension.class)
public class MemberServiceTest {
@Mock
private MemberRepository memberRepository;
@InjectMocks
private MemberService memberService;
@Test
@DisplayName("find 기능 확인")
void find() {
// given
Member member = new Member("hyunwook", "서울");
when(memberRepository.save(any())).thenReturn(member);
memberService.join(member); // service 로직
when(memberRepository.findById(1L)).thenReturn(Optional.of(member));
// when
Member result = memberService.find(1L);
// then
Assertions.assertThat(result).isNotNull();
Assertions.assertThat(result.getName()).isEqualTo("hyunwwok");
Assertions.assertThat(result.getLocation()).isEqualTo("서울");
}
}
- @Mock는 대체할 모듈위에 붙이고 @InjectMocks는 테스트할 모듈에 붙인다. 그러면 @Mock라 붙은 모둘이 @InjectMocks가 붙은 모듈에 주입된다.
- 위의 코드에서 save 메소드 실행시 실제 save 메소드는 작동하지 않고 그냥 바로 member를 돌려준다. 한마디로 save 메소드안에 아무리 복잡한 코드가 있어도 실행하지 않고 그냥 결과만을 return한다.
ArgumentCaptor
- ArgumentCaptor 을 사용하여 특정 메소드에 사용되는 아규먼트를 저장해 놓았다가 다시 사용할 수 있다.
- 쉽게 말해서, 아규먼트를 capture 했다가 getValue 로 사용한다.
@Mock
private DeveloperRepository developerRepository;
@InjectMocks
private DMakerService dMakerService;
@Test
void createDeveloperTest_success() {
//given
CreateDeveloper.Request request = CreateDeveloper.Request.builder()
.developerLevel(JUNGNIOR)
.developerSkillType(FRONT_END)
.experienceYears(7)
.memberId("memberId")
.name("name")
.age(28)
.build();
ArgumentCaptor<Developer> captor =
ArgumentCaptor.forClass(Developer.class);
//when
CreateDeveloper.Response response = dMakerService.createDeveloper(request);
//then
verify(developerRepository, times(1))
.save(captor.capture());
Developer savedDeveloper = captor.getValue();
assertEquals(JUNGNIOR, savedDeveloper.getDeveloperLevel());
assertEquals(FRONT_END, savedDeveloper.getDeveloperSkillType());
assertEquals(7, savedDeveloper.getExperienceYears());
assertEquals(JUNGNIOR, response.getDeveloperLevel());
assertEquals(FRONT_END, response.getDeveloperSkillType());
assertEquals(7, response.getExperienceYears());
}
- 위의 코드와 같이 vertify에 capture 를 전달하고 captor.getValue() 를 통해서 어떤 값이 전달됐는지 확인할 수 있다.
REFERENCES