[인프런 워밍업 스터디] Day 18 미션
인프런 ‘Readable Code: 읽기 좋은 코드를 작성하는 사고법’을 수강한 후, 작성한 내용입니다.📌 @Mock, @MockBean, @Spy, @SpyBean, @InjectMocksMock과 Spy의 차이Mock: 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체Spy: Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수도 있다.Stub이란?테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체. 그 외에는 응답하지 않는다.여기서 그러면 ‘Mock이랑 Stub이랑 무슨 차이지? 같은 내용같은데?’라는 의문이 들 수 있다.https://martinfowler.com/articles/mocksArentStubs.htmlstub은 상태 검증, 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검증하고자 하는 행동