[인프런 워밍업 스터디] Day 18 미션
인프런 ‘Readable Code: 읽기 좋은 코드를 작성하는 사고법’을 수강한 후, 작성한 내용입니다.
📌 @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks
Mock과 Spy의 차이
Mock: 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체
Spy: Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체
일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수도 있다.
Stub이란?
테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체. 그 외에는 응답하지 않는다.
여기서 그러면 ‘Mock이랑 Stub이랑 무슨 차이지? 같은 내용같은데?’라는 의문이 들 수 있다.
https://martinfowler.com/articles/mocksArentStubs.html
stub은 상태 검증, mock은 행위 검증과 관련되어 있다.
public interface MailService {
public void send (Message msg);
}
public class MailServiceStub implements MailService {
private List<Message> messages = new ArrayList<Message>();
public void send (Message msg) {
messages.add(msg);
}
public int numberSent() {
return messages.size();
}
}
// 테스트
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
MailServiceStub mailer = new MailServiceStub();
order.setMailer(mailer);
order.fill(warehouse);
assertEquals(1, mailer.numberSent());
}
Stub은 메일을 몇 번 보냈는지를 나타내는 상태를 검증한다.
// 테스트
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
Mock warehouse = mock(Warehouse.class);
Mock mailer = mock(MailService.class);
order.setMailer((MailService) mailer.proxy());
mailer.expects(once()).method("send");
warehouse.expects(once()).method("hasInventory")
.withAnyArguments()
.will(returnValue(false));
order.fill((Warehouse) warehouse.proxy());
}
}
Mock은 행위를 중심으로 검증한다.
@Mock, @MockBean
@Mock
어노테이션이 붙은 객체를 Mock 객체로 생성
@MockBean
어노테이션이 붙은 객체를 Mock 객체로 생성하고, Spring Context에 등록된 빈을 Mock 객체로 대체한다.
@Spy, SpyBean
@Spy
어노테이션이 붙은 객체를 실제 객체 기반으로 만들지만, 일부 기능만 Stubbing 할 때 사용
@SpyBean
해당 객체를 Spy 객체로 생성하고, Spring Context에 등록된 빈을 Spy 객체로 대체한다.
@InjectMocks
@InjectMocks 어노테이션이 붙은 객체가 필요로하는 필드 중 @Mock으로 생성된 객체를 주입해준다.
📌 BDD 스타일로 테스트 코드 배치하기
아래 3개의 테스트가 있다.
@BeforeEach
void setUp() {
❓
}
@DisplayName("사용자가 댓글을 작성할 수 있다.")
@Test
void writeComment() {
1-1. 사용자 생성에 필요한 내용 준비
1-2. 사용자 생성
1-3. 게시물 생성에 필요한 내용 준비
1-4. 게시물 생성
1-5. 댓글 생성에 필요한 내용 준비
1-6. 댓글 생성
// given
❓
// when
❓
// then
검증
}
@DisplayName("사용자가 댓글을 수정할 수 있다.")
@Test
void updateComment() {
2-1. 사용자 생성에 필요한 내용 준비
2-2. 사용자 생성
2-3. 게시물 생성에 필요한 내용 준비
2-4. 게시물 생성
2-5. 댓글 생성에 필요한 내용 준비
2-6. 댓글 생성
2-7. 댓글 수정
// given
❓
// when
❓
// then
검증
}
@DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.")
@Test
void cannotUpdateCommentWhenUserIsNotWriter() {
3-1. 사용자1 생성에 필요한 내용 준비
3-2. 사용자1 생성
3-3. 사용자2 생성에 필요한 내용 준비
3-4. 사용자2 생성
3-5. 사용자1의 게시물 생성에 필요한 내용 준비
3-6. 사용자1의 게시물 생성
3-7. 사용자1의 댓글 생성에 필요한 내용 준비
3-8. 사용자1의 댓글 생성
3-9. 사용자2가 사용자1의 댓글 수정 시도
// given
❓
// when
❓
// then
검증
}
내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치해야 할까?
(@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)
✔ 게시판 게시물에 달리는 댓글을 담당하는 Service Test
✔ 댓글을 달기 위해서는 게시물과 사용자가 필요하다.
✔ 게시물을 올리기 위해서는 사용자가 필요하다.
직접 코드 배치해보기
@BeforeEach
void setUp() {
사용자 생성에 필요한 내용 준비
사용자 생성
사용자의 게시물 생성에 필요한 내용 준비
사용자의 게시물 생성
}
@DisplayName("사용자가 댓글을 작성할 수 있다.")
@Test
void writeComment() {
// given
댓글 생성에 필요한 내용 준비
// when
댓글 생성
// then
검증
}
@DisplayName("사용자가 댓글을 수정할 수 있다.")
@Test
void updateComment() {
// given
댓글 생성에 필요한 내용 준비
댓글 생성
// when
댓글 수정
// then
검증
}
@DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.")
@Test
void cannotUpdateCommentWhenUserIsNotWriter() {
// given
사용자2 생성에 필요한 내용 준비
사용자2 생성
사용자1의 댓글 생성에 필요한 내용 준비
사용자1의 댓글 생성
// when
사용자2가 사용자 1의 댓글 수정 시도
// then
검증
}
공통된 준비 작업을
@BeforeEach
로 분리댓글을 달기 위한 조건 작업
게시물을 작성할 사용자 생성
게시물 생성
given
검증하고자 하는 행동에 필요한 작업 수행
댓글 생성과 관련된 내용은 테스트마다 관리할 수 있도록 given 절에 배치
when
검증하고자 하는 행동
댓글을 작성해보세요.