본문 바로가기
스프링과 JPA 기반 웹 어플리케이션 개발/6부 패키지와 테스트 정리

70. 테스트 클래스 정리

by Backchus 2020. 5. 6.

ObjectMother를 적용하자

AccountFactory와 StudyFactoryt를 만들고 테스트 픽처 메소드 옮기기

  • 상속 보다는 위임을

커스텀 애노테이션으로 테스트 애노테이션 묶음 만들기

@Retention(RetentionPilicy.RUNTIME)
@Target(ElementType.TYPE)
@Transactional
@SpringBootTest
@AutoConfigureMockMvc
public @interface MockMvcTest {
}

 

Account 생성을 도와주는 AccountFactory 클래스 생성

package me.weekbelt.studyolle.modules.account;

@Component
@RequiredArgsConstructor
public class AccountFactory {
    
    @Autowired AccountRepository accountRepository;
    
    public Account createAccount(String nickname) {
        Account account = new Account();
        account.setNickname(nickname);
        account.setEmail(nickname + "@email.com");
        accountRepository.save(account);
        return account;
    }
}

 

Study 생성을 도와주는 StudyFactory 클래스 생성

package me.weekbelt.studyolle.modules.study;

@Component
@RequiredArgsConstructor
public class StudyFactory {

    @Autowired StudyService studyService;
    @Autowired StudyRepository studyRepository;

    public Study createStudy(String path, Account manager) {
        Study study = new Study();
        study.setPath(path);
        studyService.createNewStudy(study, manager);
        return study;
    }
}

 

테스트 코드 리팩토링

공통적인 어노테이션을 모아 놓은 @MockMvcTest를 만들어주고 기존에 있던 어노테이션을 삭제하고 모든 Controller테스트에 적용시켜 준다.

package me.weekbelt.studyolle.infra;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Transactional
@SpringBootTest
@AutoConfigureMockMvc
public @interface MockMvcTest {
}
package me.weekbelt.studyolle.modules.study;

@MockMvcTest
public class StudyControllerTest {

    // ..........
    
    @Autowired AccountFactory accountFactory;
    @Autowired StudyFactory studyFactory;

    // ..........

    @Test
    @WithAccount("joohyuk")
    @DisplayName("스터디 가입")
    void joinStudy() throws Exception {
        Account weekbelt = accountFactory.createAccount("weekbelt");     // 위임
        Study study = studyFactory.createStudy("test-study", weekbelt);  // 위임

        mockMvc.perform(get("/study/" + study.getPath() + "/join"))
                .andExpect(status().is3xxRedirection())
                .andExpect(redirectedUrl("/study/" + study.getPath() + "/members"));

        Account joohyuk = accountRepository.findByNickname("joohyuk");
        assertThat(study.getMembers().contains(joohyuk)).isTrue();
    }

    @Test
    @WithAccount("joohyuk")
    @DisplayName("스터디 탈퇴")
    void leaveStudy() throws Exception {
        Account weekbelt = accountFactory.createAccount("weekbelt");     // 위임
        Study study = studyFactory.createStudy("test-study", weekbelt);  // 위임
        Account joohyuk = accountRepository.findByNickname("joohyuk");
        studyService.addMember(study, joohyuk);

        mockMvc.perform(get("/study/" + study.getPath() + "/leave"))
                .andExpect(status().is3xxRedirection())
                .andExpect(redirectedUrl("/study/" + study.getPath() + "/members"));

        assertThat(study.getMembers().contains(joohyuk)).isFalse();
    }


}
package me.weekbelt.studyolle.modules.event;

@MockMvcTest
class EventControllerTest extends StudyControllerTest {

    @Autowired MockMvc mockMvc;
    @Autowired StudyFactory studyFactory;
    @Autowired AccountFactory accountFactory;
    @Autowired EventService eventService;
    @Autowired EnrollmentRepository enrollmentRepository;
    @Autowired AccountRepository accountRepository;

    @DisplayName("선착순 모임에 참가 신청 - 자동 수락")
    @WithAccount("joohyuk")
    @Test
    public void newEnrollment_to_FCFS_event_accepted() throws Exception {
        Account weekbelt = accountFactory.createAccount("weekbelt");
        Study study = studyFactory.createStudy("test-study", weekbelt);
        Event event = createEvent("test-event", EventType.FCFS, 2, study, weekbelt);

        mockMvc.perform(post("/study/" + study.getPath() + "/events/" + event.getId() + "/enroll")
                .with(csrf()))
                .andExpect(status().is3xxRedirection())
                .andExpect(redirectedUrl("/study/" + study.getPath() + "/events/" + event.getId()));


        Account joohyuk = accountRepository.findByNickname("joohyuk");
        isAccepted(joohyuk, event);
    }

    @DisplayName("선착순 모임에 참가 신청 - 대기중 (이미 인원이 꽉차서)")
    @WithAccount("joohyuk")
    @Test
    public void newEnrollment_to_FCFS_event_not_accepted() throws Exception {
        Account weekbelt = accountFactory.createAccount("weekbelt");
        Study study = studyFactory.createStudy("test-study", weekbelt);
        Event event = createEvent("test-event", EventType.FCFS, 2, study, weekbelt);

        Account may = accountFactory.createAccount("may");
        Account june = accountFactory.createAccount("june");
        eventService.newEnrollment(event, may);
        eventService.newEnrollment(event, june);

        mockMvc.perform(post("/study/" + study.getPath() + "/events/" + event.getId() + "/enroll")
                .with(csrf()))
                .andExpect(status().is3xxRedirection())
                .andExpect(redirectedUrl("/study/" + study.getPath() + "/events/" + event.getId()));


        Account joohyuk = accountRepository.findByNickname("joohyuk");
        isNotAccepted(joohyuk, event);
    }

    @DisplayName("선착순 모임에 참가 신청 - 취소")
    @WithAccount("joohyuk")
    @Test
    public void cancelEnrollment_to_FCFS_event_not_accepted() throws Exception {
        Account joohyuk = accountRepository.findByNickname("joohyuk");
        Account weekbelt = accountFactory.createAccount("weekbelt");
        Account may = accountFactory.createAccount("may");
        Study study = studyFactory.createStudy("test-study", weekbelt);
        Event event = createEvent("test-event", EventType.FCFS, 2, study, weekbelt);

        eventService.newEnrollment(event, may);
        eventService.newEnrollment(event, joohyuk);
        eventService.newEnrollment(event, weekbelt);

        isAccepted(may, event);
        isAccepted(joohyuk, event);
        isNotAccepted(weekbelt, event);

        mockMvc.perform(post("/study/" + study.getPath() + "/events/" + event.getId() + "/disenroll")
                .with(csrf()))
                .andExpect(status().is3xxRedirection())
                .andExpect(redirectedUrl("/study/" + study.getPath() + "/events/" + event.getId()));

        isAccepted(may, event);
        isAccepted(weekbelt, event);
        assertThat(enrollmentRepository.findByEventAndAccount(event, joohyuk)).isNull();
    }

    @DisplayName("관리자 확인 모임에 참가 신청 - 대기중")
    @WithAccount("joohyuk")
    @Test
    public void newEnrollment_to_CONFIRMATIVE_event_not_accepted() throws Exception {
        Account weekbelt = accountFactory.createAccount("weekbelt");
        Study study = studyFactory.createStudy("test-study", weekbelt);
        Event event = createEvent("test-event", EventType.CONFIRMATIVE, 2, study, weekbelt);

        mockMvc.perform(post("/study/" + study.getPath() + "/events/" + event.getId() + "/enroll")
                .with(csrf()))
                .andExpect(status().is3xxRedirection())
                .andExpect(redirectedUrl("/study/" + study.getPath() + "/events/" + event.getId()));

        Account joohyuk = accountRepository.findByNickname("joohyuk");
        isNotAccepted(joohyuk, event);
    }

    private void isAccepted(Account joohyuk, Event event) {
        assertThat(enrollmentRepository.findByEventAndAccount(event, joohyuk).isAccepted()).isTrue();
    }

    private void isNotAccepted(Account joohyuk, Event event) {
        assertThat(enrollmentRepository.findByEventAndAccount(event, joohyuk).isAccepted()).isFalse();
    }

    private Event createEvent(String eventTitle, EventType eventType, int limit, Study study, Account account) {
        Event event = new Event();
        event.setEventType(eventType);
        event.setLimitOfEnrollments(limit);
        event.setTitle(eventTitle);
        event.setCreatedDateTime(LocalDateTime.now());
        event.setEndEnrollmentDateTime(LocalDateTime.now().plusDays(1));
        event.setStartDateTime(LocalDateTime.now().plusDays(1).plusHours(5));
        event.setEndDateTime(LocalDateTime.now().plusDays(1).plusHours(7));
        return eventService.createEvent(event, study, account);
    }
}

 

 

참고: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1#

 

스프링과 JPA 기반 웹 애플리케이션 개발 - 인프런

이 강좌에서 여러분은 실제로 운영 중인 서비스를 스프링, JPA 그리고 타임리프를 비롯한 여러 자바 기반의 여러 오픈 소스 기술을 사용하여 웹 애플리케이션을 개발하는 과정을 학습할 수 있습니다. 이 강좌를 충분히 학습한다면 여러분 만의 웹 서비스를 만들거나 취직에 도움이 될만한 포트폴리오를 만들 수 있을 겁니다. 중급이상 웹 개발 프레임워크 및 라이브러리 Java Spring Spring Boot Spring Data JPA Thymeleaf 온라인 강의

www.inflearn.com