🎁[속보] 인프런 내 깜짝 선물 출현 중🎁

Day 18 Mission [Layered Architecture 에 대하여 정리]

 1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 한번 정리해 봅시다.

 

1. @Mock vs @Spy (Mockito)

이 두 개는 Mockito에서 제공하는 어노테이션이고, 단위 테스트에서 가짜 객체(Mock 또는 Spy)를 생성할 때 사용된다.

@Mock

  • 완전히 가짜(Mock) 객체를 생성해서 메서드 호출 시 기본적으로 null, 0, false 같은 값을 반환한다.

  • 호출 기록을 확인할 수 있고, 특정 동작을 when(...).thenReturn(...)으로 지정 가능하다.

  • 실제 객체의 메서드는 호출되지 않는다.

예제:

@Mock
private UserRepository userRepository;

@Test
void testMock() {
    when(userRepository.findById(1L)).thenReturn(Optional.of(new User(1L, "John")));

    Optional<User> user = userRepository.findById(1L);
    System.out.println(user.get().getName()); // John
}

@Spy

  • 실제 객체를 감싸는(Mock하지 않는) 가짜 객체(Spy) 생성한다.

  • 기본적으로 실제 객체의 메서드를 실행하지만, 특정 메서드는 when(...).thenReturn(...)을 사용하여 동작을 변경할 수 있다.

  • 일부 동작만 모킹하고 싶을 때 유용하다.

예제:

@Spy
private List<String> spyList = new ArrayList<>();

@Test
void testSpy() {
    spyList.add("one");
    spyList.add("two");

    when(spyList.size()).thenReturn(100); // 특정 메서드만 모킹

    System.out.println(spyList.get(0)); // one (실제 메서드 호출)
    System.out.println(spyList.size()); // 100 (모킹된 값 반환)
}

2. @MockBean vs @SpyBean (Spring Boot)

이 두 개는 Spring Boot에서 제공하는 어노테이션이다.
Spring 컨테이너에 있는 빈(Bean)을 Mock 또는 Spy로 대체하는 역할을 한다.

@MockBean

  • Spring 컨텍스트에서 기존에 등록된 빈(Bean)을 Mock 객체로 교체.

  • 모든 메서드는 기본적으로 null, 0, false를 반환한다.

  • @Mock과 비슷하지만, Spring 컨테이너에 등록된 Bean을 대상으로 한다는 차이점이 있다.

예제:

@SpringBootTest
class MyServiceTest {
    
    @MockBean
    private UserRepository userRepository;

    @Autowired
    private MyService myService;

    @Test
    void testMockBean() {
        when(userRepository.findById(1L)).thenReturn(Optional.of(new User(1L, "John")));

        User user = myService.getUserById(1L);
        System.out.println(user.getName()); // John
    }
}

MockBean을 사용하면 UserRepository의 실제 빈이 Mock으로 대체됨.


@SpyBean

  • Spring 컨텍스트에서 기존에 등록된 빈(Bean)을 Spy 객체로 교체된다.

  • 기본적으로 실제 객체의 메서드를 실행하지만, 특정 메서드는 when(...).thenReturn(...)을 사용해 동작을 변경할 수 있다.

  • @Spy와 비슷하지만, Spring 컨테이너의 실제 Bean을 대상으로 한다는 점이 다르다.

예제:

@SpringBootTest
class MyServiceTest {

    @SpyBean
    private UserService userService; // 기존 Bean을 Spy로 감싸서 일부 메서드만 Mock 가능

    @Test
    void testSpyBean() {
        when(userService.getUserName(1L)).thenReturn("Mocked Name");

        System.out.println(userService.getUserName(1L)); // Mocked Name (모킹된 메서드)
        System.out.println(userService.getUserCount());  // 실제 메서드 실행
    }
}

SpyBean을 사용하면 UserService의 실제 빈이 Spy로 감싸짐.


3. @InjectMocks

  • @Mock이나 @Spy로 생성된 객체들을 자동으로 주입해 줌.

  • 테스트 대상 클래스에 의존하는 객체들을 자동으로 설정해 주기 때문에, new 키워드 없이 의존성을 주입할 수 있음.

  • @Mock 또는 @Spy로 선언된 객체를 @InjectMocks가 붙은 객체의 생성자, 필드, setter 등을 이용해 주입함.

예제:

class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public String getUserName(Long id) {
        return userRepository.findById(id).map(User::getName).orElse("Unknown");
    }
}

class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService; // userRepository가 자동 주입됨

    @Test
    void testInjectMocks() {
        when(userRepository.findById(1L)).thenReturn(Optional.of(new User(1L, "Alice")));

        System.out.println(userService.getUserName(1L)); // Alice
    }
}

userService를 직접 new로 생성하지 않아도 @InjectMocks@MockuserRepository를 자동으로 주입해 준다.

 

2. 아래 3개의 테스트가 있습니다.
내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요?
(@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)

  • 댓글이 목적이니 댓글 이외의 통합할수 있는 부분은 통합처리

@BeforeEach 
void setUp() {
    1-1. 2-1. 3-1. 사용자 생성에 필요한 내용 준비
    1-3. 2-3. 3-5. 게시물 생성에 필요한 내용 준비
    1-2. 2-2. 3-2. 사용자 생성
    1-4. 2-4. 3-6. 게시물 생성
}

@DisplayName("사용자가 댓글을 작성할 수 있다.")
@Test
void writeComment() {

    // given
    1-5. 댓글 생성에 필요한 내용 준비

    // when
    1-6. 댓글 생성

    // then
    검증
}

@DisplayName("사용자가 댓글을 수정할 수 있다.")
@Test
void updateComment() {

    // given
    2-5. 댓글 생성에 필요한 내용 준비
    2-6. 댓글 생성

    // when
    2-7. 댓글 수정

    // then
    검증
}

@DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.")
@Test
void cannotUpdateCommentWhenUserIsNotWriter() {

    // given
    3-3. 사용자2 생성에 필요한 내용 준비
    3-4. 사용자2 생성
    3-7. 사용자1의 댓글 생성에 필요한 내용 준비
    3-8. 사용자1의 댓글 생성

    // when
    3-9. 사용자2가 사용자1의 댓글 수정 시도

    // then
    검증        
}

댓글을 작성해보세요.


채널톡 아이콘