Post

Mock

Mock

Mock 이란?

Mock은 진짜 객체와 비슷하게 동작하지만 개발자가 직접 그 객체의 행동을 관리하는 객체이다.

객체를 테스트할 때 사용한다.




Mocking

Mocking은 Unit Test에서 주로 등장하며 테스트 대상 객체가 의존하고 있는 다른 객체들을 가짜 객체로 만드는 것이다.

테스트 대상은 다른 객체나 함수 간의 의존성을 가질 수 있다.

회원 가입을 하기 위해서는 ID와 PW를 저장하면 되지만

실제로는 ID 중복 체크, 비밀번호 유효성 검사, 암호화 등 하나의 행동에 다양한 의존 관계가 존재한다.


하지만 ID와 PW가 제대로 저장되는지만 테스트하고 싶다면

ID 중복 체크는 항상 true, 비밀번호 유효성 검사는 항상 true, 암호화 결과는 ‘a123’이라고 가정해볼 때

DB insert 이전 객체의 데이터가 { ID: 'idid', PW: 'a123' } 라면 정상적으로 회원 가입 로직이 수행된 것으로 볼 수 있다.

회원 가입이라는 행동에 의존된 것들을 가짜 행위로 가정한 것이 Mocking이다.


테스트 대상이 아닌 것들을 Fake Object 또는 Fake Action으로 처리하는 것을 의존성을 Mocking 처리했다고 말한다.

Mocking은 테스트 대상 객체의 행동을 고립시키기 위해 의존되어 있는 객체들의 행동을 가짜로 만들어 주는 것이다.




Mockito

Mockito는 단위 테스트를 위한 Java Mocking Framework이다.

Spring Boot 2.2 이상에서는 spring-boot-starter-test에 Mockito가 포함되어 있다.

Mockito 없이 직접 Mock을 구현할 수도 있지만 의존성의 개수와 크기에 따라 구현 부담이 커진다는 단점이 있다.



1. Mockito를 이용하여 Mock 생성하기

1
2
3
4
5
6
7
8
9
10
11
12
13
class CommentServiceTest {
    @Test
    void mockito_test() {
        UserRepository userRepository = mock(UserRepository.class);
        RegistryRepository registryRepository = mock(RegistryRepository.class);
        CommentRepository commentRepository = mock(CommentRepository.class);

        CommentServiceImpl commentService =
                new CommentServiceImpl(commentRepository, registryRepository, userRepository);

        assertThat(commentService).isNotNull();
    }
}



2. @Mock 어노테이션으로 생성하기

JUnit5 Extension을 사용하기 위해 테스트 클래스에 @ExtendWith(MockitoExtension.class)를 선언한다.

1
2
3
@ExtendWith(MockitoExtension.class)
class RegistryServiceTest {
}

@ExtendWith(MockitoExtension.class)는 해당 테스트 클래스가 Mockito를 사용함을 의미한다.

@Mock 사용 방법은 생성자 주입 방식과 @InjectMocks를 통한 자동 주입 방식 두 가지가 있다.



방법 1. 주입된 @Mock 객체를 직접 생성자에 주입

1
2
3
4
5
6
7
8
9
10
11
12
13
@ExtendWith(MockitoExtension.class)
class RegistryServiceTest {

    @Test
    void mockito_test(@Mock UserRepository userRepository,
                      @Mock RegistryRepository registryRepository) {

        RegistryServiceImpl registryService =
                new RegistryServiceImpl(registryRepository, userRepository);

        assertThat(registryService).isNotNull();
    }
}

@Mock은 실제 구현 객체 대신 Mock 객체를 사용하겠다는 의미이다.

테스트 런타임 시 실제 객체 대신 Mock 객체가 주입되어 Unit Test가 수행된다.



방법 2. @InjectMocks를 통해 자동 주입

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ExtendWith(MockitoExtension.class)
class RegistryServiceTest {

    @Mock
    UserRepository userRepository;

    @InjectMocks
    RegistryServiceImpl registryService;

    @Test
    void mockito_test() {
        assertThat(registryService).isNotNull();
    }
}

@InjectMocks는 @Mock으로 생성된 객체들을 대상 클래스의 생성자 또는 필드에 자동으로 주입한다.

테스트 런타임 시 클래스 내부 멤버 변수 중 @Mock으로 등록된 타입과 일치하는 곳에 Mock 객체가 주입된다.


RegistryServiceTest는 Mockito를 사용하고

UserRepository를 실제 객체 대신 Mock 객체로 주입하며 RegistryServiceImpl의 의존성이 Mock 객체로 해결된다.





@MockBean

@MockBeanorg.springframework.boot.test.mock.mockito 패키지에 포함된 Spring Boot 전용 어노테이션이다.

Mockito의 Mock 객체를 Spring ApplicationContext에 등록하고 동일 타입의 Bean이 존재하면 해당 Bean을 MockBean으로 교체한다.

@MockBean은 스프링 컨텍스트에 등록되므로 @Autowired를 통해 주입받을 수 있다.



@Mock 과 @MockBean 사용 기준

Spring Boot Container가 필요하고 Bean이 컨텍스트에 등록되어 있어야 한다면 @MockBean을 사용한다.

단순 Unit Test이며 Spring Context가 필요 없다면 @Mock을 사용한다.

@Mock@InjectMocks를 통해서만 의존성 주입이 가능하다.

@MockBean은 Spring Context에 등록되기 때문에 @SpringBootTest와 함께 @Autowired로 주입된다.


Mock 종류의존성 주입
@Mock@InjectMocks
@MockBean@Autowired

@Mock을 사용하면 @InjectMocks로 주입하고 @MockBean을 사용하면 @Autowired로 주입한다.

Spring Boot가 아닌 순수 Java 환경에서는 @Mock만 사용 가능하다.





Mockito Test

Mockito 테스트는 CreateMock, Stub, Exercise, Verify 순서로 진행된다.

CreateMock : Mock 객체를 생성하는 단계

Stub : Mock 객체의 동작을 정의하는 단계

Exercise : 테스트 대상 메소드를 실행하는 단계

Verify : 메소드 호출 여부와 횟수를 검증하는 단계



Stubbing 이란?

Stubbing은 Mock 객체의 메소드가 호출되었을 때 어떤 값을 반환할지 정의하는 것이다.

테스트가 통과할 정도로만 동작을 구현하는 것이 특징이다.



Mock 과 Stub 차이

Controller 테스트에서는 Service의 내부 동작이 아닌 응답 결과만 중요하다.

이 경우 Service의 실제 구현은 필요 없고 정적인 반환 데이터만 필요하다.

이런 상황을 Stubbing이라고 한다.


Stub은 단순 테스트용이며 객체의 정확한 동작 검증은 하지 않는다.

Mock은 테스트 대상이 의존하는 객체가 올바르게 사용되었는지까지 검증한다.



OngoingStubbing

1
when(스터빙할_메소드).thenReturn(리턴값);


메소드설명
thenReturn지정한 객체 반환
thenThrow예외 발생
thenAnswer커스텀 동작 정의
thenCallRealMethod실제 메소드 호출



Stubber

1
doReturn(값).when(객체).메소드();
메소드설명
doReturn값 반환
doThrow예외 발생
doAnswer커스텀 동작
doNothing아무 동작도 하지 않음
doCallRealMethod실제 메소드 호출



any()

내부에서 객체를 생성하여 매개변수를 제어할 수 없는 경우 any()를 사용한다.

1
when(registryRepository.save(any(Registry.class))).thenReturn(saveRegistry);



verify 검증

verify를 통해 메소드 호출 여부와 횟수를 검증할 수 있다.

1
verify(mock, times(1));
메소드설명
times(n)n번 호출
never호출 안 됨
atLeast(n)최소 n번
atMost(n)최대 n번



Mockito 적용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@ExtendWith(MockitoExtension.class)
class RegistryServiceTest {

    @Mock
    UserRepository userRepository;

    @Mock
    RegistryRepository registryRepository;

    @InjectMocks
    RegistryServiceImpl registryService;

    @Test
    void register() {
        RegistryDto registryDto = new RegistryDto();
        registryDto.setTitle("첫 번째");
        registryDto.setMain("1");

        Registry saveRegistry = registryDto.toEntity(null);

        when(registryRepository.save(any(Registry.class))).thenReturn(saveRegistry);

        Registry registry = registryService.postUpload(registryDto, null);

        verify(registryRepository).save(any(Registry.class));

        Assertions.assertThat(registryDto.getTitle()).isEqualTo(registry.getTitle());
    }
}





REFERENCE
Mockito 알아보기 (부제 : BDD)
[Java] Mockito 사용법 (3) - 스터빙 (Stubbing) (OngoingStubbing, Stubber)
[Java] Mockito 사용법 (4) - 검증 (Verify)
[TDD] Fixture와 Mock이란?
Mockito란?

This post is licensed under CC BY 4.0 by the author.