이때 사용되는 것이 Mockito
프레임워크이다.
Mockito
는 개발자가 제어 가능한 가짜 객체의 의존성을 주입하게 도와준다.**
Member
@Entity
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 20)
private String username;
}
MemberController
@RestController
@RequiredArgsConstructor
@RequestMapping("/members")
public class MemberController {
private final MemberService memberService;
@PostMapping
public MemberResponseDTO.CreateResultDTO createMember(
@RequestBody MemberRequestDTO.CreateDTO request
) {
Member newMember = memberService.createMember(request);
return MemberResponseDTO.CreateResultDTO.toCreateResultDTO(newMember);
}
@GetMapping("/{memberId}")
public MemberResponseDTO.PreviewDTO getMember(
@PathVariable Long memberId
) {
Member findMember = memberService.getMember(memberId);
return MemberResponseDTO.PreviewDTO.toPreviewDTO(findMember);
}
}
MemberService
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
public Member createMember(MemberRequestDTO.CreateDTO request) {
return memberRepository.save(request.toMember());
}
public Member getMember(Long memberId) {
return memberRepository.findById(memberId).get();
}
}
MemberRepository
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
}
build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// h2
implementation 'com.h2database:h2'
// jpa
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
//mysql
implementation 'mysql:mysql-connector-java:8.0.33'
// web
implementation 'org.springframework.boot:spring-boot-starter-web'
// lombok
implementation 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
//swaggerDoc
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
// Gson
implementation 'com.google.code.gson:gson:2.8.9'
}
레포지토리는 따로 의존성이 없고 직접 DB와 연결이 있어야 하기에
인메모리 H2 데이터 베이스를 이용한다.
여기서 사용되는 @DataJpaTest
어노테이션이 인메모리 h2데이터 베이스를 이용케 한다. 또한 테스트가 끝나면 자동으로 롤백해주어 테스트를 용이 하게 한다.
MemberRepositoryTest
@DataJpaTest
class MemberRepositoryTest {
private final MemberRepository memberRepository;
@Autowired
public MemberRepositoryTest(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Test
public void saveMemberTest() {
// given
String username = "test";
// when
memberRepository.save(Member.builder()
.username(username)
.build());
// then
List<Member> allMember = memberRepository.findAll();
assertEquals(1, allMember.size());
assertEquals(username, allMember.stream().findAny().get().getUsername());
}
}
서비스 계층은 HTTP 통신과는 관계 없이 로직 검증만 하면 된다.
위 서비스 코드는 MemberService 코드는 MemberRepository 코드를 의존 하고 있다.
단위 테스트를 하기 위해서 Mockito
를 이용하여 다른 계층과 의존관계를 단절 시켜 주어야한다.
서비스가 의존중인 MemberRepository를 Mock객체를 이용해 주입 시켜 주자
// Mockito를 사용하기 위한 어노테이션
@ExtendWith(MockitoExtension.class)
class MemberServiceTest {
@Mock // 가짜 객체를 만들어줌
private MemberRepository memberRepository;
@InjectMocks // memberRepository를 memberService에 주입
private MemberService memberService;
...
}
위처럼 가짜 객체를 주입 시켜 주었다면 가짜 객체가 하는 일을 코드로 정의 하여야 한다.
어떤 결과를 반환하라고 정해진 답변을 준비시켜야 한다
이때 사용되는 방법중 두가지
when.thenReturn
(리턴할 객체)doReturn.when
(메서드)필자는 when.thenReturn
문법을 사용하였다.
// Mockito를 사용하기 위한 어노테이션
@ExtendWith(MockitoExtension.class)
class MemberServiceTest {
@Mock // 가짜 객체를 만들어줌
private MemberRepository memberRepository;
@InjectMocks // memberRepository를 memberService에 주입
private MemberService memberService;
@Test
void createMember() {
Long memberId = 1L;
String username = "test";
MemberRequestDTO.CreateDTO request = MemberRequestDTO.CreateDTO.builder()
.username(username)
.build();
Member testMember = Member.builder()
.id(memberId)
.username(username)
.build();
// memberRepository.save(Member)가 호출되면 newMember를 리턴하라는 의미
when(memberRepository.save(any(Member.class))).thenReturn(testMember);
// 또는
// doReturn(testMember).when(memberRepository).save(any(Member.class));
// memberService.createMember 호출
Member newMember = memberService.createMember(request);
assertNotNull(newMember);
assertEquals(memberId, newMember.getId());
assertEquals(username, newMember.getUsername());
}
}
@Test
void getMember() {
Long memberId = 1L;
String username = "test";
Member expectedMember = Member.builder()
.id(memberId)
.username(username)
.build();
// memberRepository.findById(memberId)가 호출되면 expectedMember를 리턴하라는 의미
when(memberRepository.findById(memberId)).thenReturn(Optional.of(expectedMember));
// memberService.getMember 호출
Member findMember = memberService.getMember(memberId);
// 검증
assertNotNull(findMember);
assertEquals(expectedMember.getId(), findMember.getId());
assertEquals(expectedMember.getUsername(), findMember.getUsername());
}
컨트롤러의 단위 테스트를 하기 위해서 Mockito를 이용하여 다른 계층과 의존관계를 단절 시켜 주어야한다
컨트롤러를 테스트 하기 위해서는 HTTP
호출이 필요
스프링 부트는 컨트롤러 테스트를 위한 @WebMvcTest
어노테이션을 제공
@WebMvcTest
란?WebMvcTest 어노테이션은 단위 테스트를 위한 Spring MVC 테스트에 사용된다.
인자로 Bean 객체를 받아서 (보통 Controller 객체)
전체 Spring Bean 구성중 테스트에 관련된 구성들만 적용한다! (테스트를 빠르게 해줄수 있는 이유!)
MockMvc
객체를 자동으로 생성 해준다!!ControllerAdvice
나 Filter
, Interceptor
등 웹 계층 테스트에 필요한 요소들을 모두 빈으로 등록해 웹 테스트 환경을 자동으로 만들어준다.나머지 종속성은 Mock 객체 (가짜 객체)를 통하여 제공해야 한다!!
💡 WebMvcTest는 SpringBoot가 제공하는 것이니Mock → MockBean 을 사용한다.
가짜객체만 사용하는게 아닌 가짜 Bean 객체를 사용해야 하므로!
// 아래는 맨 밑 자료를 다시 한번 보고 사용할지 말지 결정하자
**@MockBean(JpaMetamodelMappingContext.class)**
// WebMvcTest는 @Controller, @RestController가 붙은 클래스를 테스트하기 위한 어노테이션
@WebMvcTest(MemberController.class)
class MemberControllerTest {
@Autowired
public MemberControllerTest(MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
private MockMvc mockMvc;
@MockBean
private MemberService memberService;
// Gson은 JSON 형식의 문자열을 자바 객체로 변환해주는 라이브러리
@Autowired
private Gson gson;
...
}
@DisplayName("회원 생성 테스트")
@Test
@DisplayName("회원 생성 테스트")
void createMember() throws Exception {
MemberRequestDTO.CreateDTO request = MemberRequestDTO.CreateDTO.builder()
.username("이원찬").build();
Member responseUser = Member.builder()
.id(1L)
.username(request.getUsername())
.build();
when(memberService.createMember(any(MemberRequestDTO.CreateDTO.class)))
.thenReturn(responseUser);
// post "/members" 요청을 보내서 회원을 생성한다.
mockMvc.perform(MockMvcRequestBuilders.post("/members")
.content(gson.toJson(request))
.contentType("application/json"))
.andExpect(status().isOk())
.andExpect(content().json(gson.toJson(responseUser)));
}
@DisplayName("회원 조회 테스트")
void getMember() throws Exception {
String username = "이원찬";
Member responseUser = Member.builder()
.id(1L)
.username(username)
.build();
when(memberService.getMember(1L))
.thenReturn(responseUser);
// get "/members/1" 요청을 보내서 회원 정보를 가져온다.
mockMvc.perform(MockMvcRequestBuilders.get("/members/" + responseUser.getId()))
.andExpect(status().isOk())
.andExpect(content().json(gson.toJson(responseUser)));
}
[Spring] Junit @WebMvcTest 의 JPA metamodel must not be empty! 에러!