[Spring] Mockito๋?
๋จ์ ํ ์คํธ๋?
๋จ์ ํ ์คํธ๋ ํ๋์ ๋ชจ๋์ ๊ธฐ์ค์ผ๋ก ๋ ๋ฆฝ์ ์ผ๋ก ์งํ๋๋ ๊ฐ์ฅ ์์ ๋จ์์ ํ ์คํธ์ ๋๋ค. ์ฆ, ํ๋์ ๊ธฐ๋ฅ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๋์ํ๋์ง ๋ ๋ฆฝ์ ์ผ๋ก ํ ์คํธํ๋ ๊ฒ์ ๋๋ค.
ํ๋ก๊ทธ๋จ์ ์์ ๋จ์๋ก ์ชผ๊ฐ์ ๊ฐ ๋จ์๊ฐ ์ ํํ๊ฒ ๋์ํ๋์ง ๊ฒ์ฌํ๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ๋ฐ์ ์ ์ ํํ๊ฒ ์ด๋ ๋ถ๋ถ์ด ์๋ชป๋์๋์ง ์บ์นํ ์ ์๋ค๋ ์ฅ์ ์ด ์์ง๋ง, ๋ค๋ฅธ ๊ฐ์ฒด์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๊ฒฝ์ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด Service ๊ณ์ธต์ ๋จ์ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ ๋ ๋ณดํต ๋ค๋ฅธ ๊ฐ์ฒด๋ค๊ณผ ์์กด ๊ด๊ณ๋ฅผ ๋งบ๊ณ ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์ด๋, Bean Container์ ์ฃผ์ ๋ ์ค์ ๊ฐ์ฒด๋ค์ ๊ฐ์ ธ๋ค ์ฐ๋ ๋ฐฉ๋ฒ์ด ์๊ณ , Mockito ํ๋ ์์ํฌ ๋ฑ์ ํ์ฉํด Mock ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ์ฐ๋ ๋ฐฉ๋ฒ์ด ์กด์ฌํฉ๋๋ค.
Mockito๋?
Mockito๋ *JUnit Test์์ *Mock์ ์ํ Java ์คํ์์ค ํ ์คํธ ํ๋ ์์ํฌ์ ๋๋ค.
Spring์ ์ฌ๋ฌ ๊ฐ์ฒด๋ค ๊ฐ์ ์์กด์ฑ์ด ์๊ธฐ๊ฒ ๋๋๋ฐ, ์ด๋ฌํ ํน์ฑ์ ๋จ์ ํ ์คํธ๋ฅผ ์์ฑํ๊ธฐ ์ด๋ ต๊ฒ ๋ง๋ญ๋๋ค. ๋ฐ๋ผ์ ์์กด์ฑ์ ํด๊ฒฐํ๊ธฐ ์ํด ๊ฐ๋ฐ์๊ฐ ๋์์ ์ง์ ์ ์ดํ ์ ์๋ ๊ฐ์ง ๊ฐ์ฒด์ธ Mock์ ์์ฑํ์ฌ ์ฃผ์ ์์ผ์ฃผ๋ Mockito ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํฉ๋๋ค.
์ฆ, Mockito๋ Mock ๊ฐ์ฒด๋ฅผ ์ฝ๊ฒ ๋ง๋ค๊ณ , ๊ด๋ฆฌํ๊ณ , ๊ฒ์ฆํ ์ ์๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ ํ๋ ์์ํฌ์ ๋๋ค.
*Mock: ์ง์ง ๊ฐ์ฒด์ ๋น์ทํ๊ฒ ๋์ํ์ง๋ง, ํ๋ก๊ทธ๋๋จธ๊ฐ ์ง์ ํ๋์ ๊ด๋ฆฌํ๋ ๊ฐ์ฒด
*JUnit: ์๋ฐ ๋จ์ ํ ์คํธ๋ฅผ ์ํ ํ๋ ์์ํฌ
Mockito ์์ํ๊ธฐ
Dependency ์ถ๊ฐ
์คํ๋ง ๋ถํธ 2.2 ์ด์ ๋ฒ์ ์ ํ๋ก์ ํธ ์์ฑ์ spring-boot-start-test์์ ์๋์ผ๋ก Mockito๋ฅผ ์ถ๊ฐํด์ค๋๋ค.
dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
์ง์ ์ถ๊ฐํ๊ธฐ ์ํด์ mockito-core์ mockito-junit-jupiter๊ฐ ํ์ํฉ๋๋ค.
- mockito-core: mockito๊ฐ ์ ๊ณตํด์ฃผ๋ ๊ธฐ๋ณธ์ ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- mockito-junit-jupiter: junit ํ ์คํธ์์ mockito๋ฅผ ์ฐ๋ํด์ ์ฌ์ฉํ ์ ์๋ junit ํ์ฅ ๊ตฌํ์ฒด
// build.gradle
dependencies {
testImplementation 'org.mockito:mockito-core:3.11.2'
testImplementation 'org.mockito:mockito-junit-jupiter:3.11.2'
}
//maven
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
JUnit๊ณผ ๊ฒฐํฉ
Mockito๋ ํ ์คํ ํ๋ ์์ํฌ์ด๊ธฐ ๋๋ฌธ์ JUnit๊ณผ ๊ฒฐํฉ๋๊ธฐ ์ํด ํด๋์ค ์ด๋ ธํ ์ด์ ์ ๋ถ์ด๋ ๋ณ๋์ ์์ ์ด ํ์ํฉ๋๋ค.
- JUnit4: @RunWith(MockitoJUnitRunner.class)
- JUnit5: @ExtendWith(MockitoExtension.class)
SpringBoot 2.2.0๋ถํฐ ๊ณต์์ ์ผ๋ก JUnit5๋ฅผ ์ง์ํจ์ ๋ฐ๋ผ @ExtendWith(MockitoExtension.class)๋ฅผ ์ฌ์ฉํด ๊ฒฐํฉํ๊ฒ ๋ฉ๋๋ค.
@ExtendWith(MockitoExtension.class)
class Test {
// ํ
์คํธ ์์ฑ
}
Mockito ์ฌ์ฉํ๊ธฐ
์ฐ์ ํ ์คํธ ์์ฑ์ ์์ Member๋ผ๋ ๊ฐ์ฒด๋ฅผ ์กฐํํ๊ธฐ ์ํ ์ธํ ์ ์งํํฉ๋๋ค.
(ํธ์์ฑ์ ์ํด ์ ๋ Lombok์ ์ฌ์ฉํ์ต๋๋ค.)
Member
package com.example.demotest.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Getter
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "member")
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_no")
private Long no;
@Setter
@Column(name = "name", nullable = false)
private String name;
}
- Member Entity๋ฅผ ์์ฑ
Repository
import com.example.demotest.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
}
- Member๋ฅผ ์กฐํํ๊ธฐ ์ํ Repository๋ฅผ ์์ฑ
Service
import com.example.demotest.domain.Member;
import com.example.demotest.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MemberService {
private final MemberRepository memberRepository;
public List<Member> findAllMember() {
return memberRepository.findAll();
}
}
- Member List๋ฅผ ์กฐํํ๋ ์๋น์ค๋ฅผ ์์ฑ
๋ค์์ผ๋ก MemberService๋ฅผ ํตํด Member List๋ฅผ ์กฐํํ๋ Service Test ์์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
import com.example.demotest.domain.Member;
import com.example.demotest.repository.MemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.assertj.core.api.Assertions;
import java.util.ArrayList;
import java.util.List;
@ExtendWith(MockitoExtension.class)
class MemberServiceTest {
@InjectMocks
private MemberService memberService;
@Mock
private MemberRepository memberRepository;
@Test
@DisplayName("ํ์ ์กฐํ ๊ฒฐ๊ณผ Member ๋ฆฌ์คํธ๊ฐ ๋ฐํ๋์ด์ผ ํ๋ค.")
void findAllMember() {
// given
List<Member> memberList = new ArrayList<>();
memberList.add(new Member(
1L,
"name1"));
memberList.add(new Member(
2L,
"name2"));
// when
Mockito.when(memberRepository.findAll()).thenReturn(memberList);
List<Member> result = memberService.findAllMember();
// then
Assertions.assertThat(result).hasSize(2);
Assertions.assertThat(result.get(0).getName()).isEqualTo("name1");
Assertions.assertThat(result.get(1).getName()).isEqualTo("name2");
}
}
- @Mock
- Mock๊ฐ์ฒด๋ฅผ ์์ฑ
- @InjectMocks
- @Mock์ด ๋ถ์ Mock๊ฐ์ฒด๋ฅผ @InjectMocks์ด ๋ถ์ ๊ฐ์ฒด์ ์ฃผ์
- Service๋ฅผ ํ ์คํธํ๊ธฐ ์ํด์ MemberRespository๋ฅผ ์ฃผ์ ๋ฐ์์ผ ํจ
- Mock ๊ฐ์ฒด Stubbing
- when() -> memberRepository๋ฅผ ํตํด ํ์ ๋ฆฌ์คํธ๋ฅผ ์กฐํ ํ์๋
- thenReturn() -> memberList๊ฐ return ๋์ด์ผ ํจ
- Test ์งํ
- Repository์์ ์กฐํํ ๊ฒฐ๊ณผ์ Service๋ฅผ ํตํด ์กฐํํ ๊ฒฐ๊ณผ๋ฅผ ๋น๊ต
- ์์ฑํ MemberList์ ํฌ๊ธฐ๋ 2
- ์ฒซ๋ฒ์งธ ๊ฐ์ ์ด๋ฆ์ name1
- ๋๋ฒ์งธ ๊ฐ์ ์ด๋ฆ์ name2
ํ ์คํธ๋ฅผ ์คํ ๊ฒฐ๊ณผ passed๊ฐ ์ ๋์ต๋๋ค!
์์ฑ ์ฝ๋๋ ์๋ ๋งํฌ๋ฅผ ํตํด ํ์ธํ ์ ์์ต๋๋ค.
https://github.com/Jisue/Spring-Test/tree/main/demo-test
[์ฐธ๊ณ ]
https://mangkyu.tistory.com/145
https://scshim.tistory.com/439
https://www.nextree.io/mockito/
https://lemontia.tistory.com/951
https://velog.io/@hellonayeon/spring-boot-service-layer-unit-testcode