GDSC web - JUnit 을 활용한 테스트 코드

이유석·2022년 12월 11일
1

gdsc

목록 보기
4/6
post-thumbnail

카드뉴스

Test 코드

Test 코드 란???

  • 소프트웨어의 제품 or 서비스의 품질을 확인하거나, 소프트웨어의 버그를 찾을 때 작성하는 코드를 의미합니다.

  • 다시 말해, 제품이 예상하는 대로 동작 하는지 확인하는 것!

Test 코드를 작성하는 이유

  • 기능이 정상적으로 동작하는지를 확인할 수 있다.
  • 결함을 사전에 발견할 수 있다.
  • Refactoring 에 대한 자신감
  • 문서로서 작용할 수 있습니다.

Test Pyramid

  • 프로젝트를 진행할 때 공통적으로 작성해야 하는 테스트들의 집합 입니다.

Unit Test (단위 테스트)

  • 함수나 모듈 클래스와 같은 단위 딱 하나를
    테스트 하는 것을 의미합니다.

  • 예를 들어, 자전거에서 바퀴 하나만을 테스트 하는 것 입니다.

  • Spring : 블로그 서비스에서, 게시글 저장 repository 코드 테스트

Integration Test (통합 테스트)

  • 여러 가지 단위들을 통합시켰을 때,
    서로 상호작용을 작 하는지 테스트하는 것 을 의미합니다.

  • 예를 들어, 자전거에서 바퀴와 체인의 연결성을 테스트 하는 것 입니다.

  • Spring : 블로그 서비스에서, 게시글 저장에 대한 controller, service, repository 의 연결성 테스트

User Interface Test

  • 실제 사용자가 앱을 사용했을 때의
    사용자 흐름에 맞추어 테스트하는 것 을 의미합니다.

  • 예를 들어, 자전거 탑승 부터, 핸들을 잡고, 페달을 밟아가며 전체적으로 테스트 하는 것 입니다.

  • Spring : 실제 REST API 호출을 하며 테스트 해 보는 것.

Test 코드 작성 팁 (given - when - then 패턴)

  • Test 코드 작성 시, 가장 추천받는 코딩 스타일 입니다.

  • given

    • 테스트를 위해 주어진 상태
    • 테스트 대상에게 주어진 조건
    • 테스트가 동작하기 위해 주어진 환경
  • when

    • 테스트 대상에게 가해진 어떠한 상태
    • 테스트 대상에게 주어진 어떠한 조건
    • 테스트 대상의 상태를 변경시키기 위한 환경
  • then

    • given, when 에 따른 기대되어지는 결과

즉, 어떤 상태에서 출발 (given)하여 해당 상태에 어떤 변화를 가했을 때 (when)
기대하는 어떠한 상태가 되어야 합니다. (then)

TDD(Test Driven Development) 란?

정의

  • "테스트가 개발을 주도한다." 라는 개념으로 사용되는 단어입니다.
  • 즉, 테스트 코드를 먼저 작성하고, 해당 테스트를 통과하기 위한 모든 행동(코드)들이 개발되는 것을 목표로 하는 개발 방법 론 입니다.

TDD 가 필요한 상황

  1. 처음해보는 프로그램 주제 (불확실성이 높을 경우)
  2. 고객의 요구조건이 바뀔 수 있는 프로젝트 (외부적인 불확실성이 높을 경우)
  3. 개발하는 중에 코드를 많이 바꿔야 된다고 생각하는 경우

Java Test 코드

Junit - org.junit

정의

  • Java를 위한 단위 테스트 라이브러리입니다.
    즉, Java 어플리케이션에 대한 단위 테스트를 쉽게 해주는 테스트용 프레임워크

특징

  • assert 메서드(단정문)를 사용하여 테스트의 수행 결과를 판별해 알려줍니다.

    단정문 : 테스트의 성공과 실패를 판별하는 문장 입니다.

  • 다양한 Annotation의 지원으로 테스트가 간결해집니다.
  • 테스트 결과를 Test 클래스로 남김으로써, 테스트 History를 저장할 수 있습니다.

assert 메서드 예제 코드

@Test // 테스트 메서드 임을 명시
@DisplayName("Assertion 이용해보기") // 테스트 이름 표시
void useAssertion() {

    // assertTrue(조건식) - 다음 조건식이 true 인지 확인
    Assertions.assertTrue(true);
  
    // assertEquals(기대 값, 실제 값) - 실제 값이 기대 값과 같은지 확인
    Assertions.assertEquals("expected", "expected");
   
    // assertNotNull(값) - 해당 값이 null 이 아닌지 확인
    Assertions.assertNotNull(new Object());
   
    // assertThrows(기대하는 예외 타입, 실행 코드) 
    // - 실행 코드에서 해당 예외 타입이 발생하는지 확인
    Assertions.assertThrows(RuntimeException.class, () -> makeRuntimeException());

    // assertTimeOut(끝나야하는 시간, 실행 코드) - 실행 코드가 끝나야 한느 시간 안에 끝나는지 확인
    // 10초 안에 끝나는지 확인
    Assertions.assertTimeout(Duration.ofSeconds(10), () -> timeOutMethod());

    // assertAll(한번에 확인하고 싶은 모든 assertion)
    Assertions.assertAll(
          () -> Assertions.assertTrue(true),
          () -> Assertions.assertTrue(true),
          () -> Assertions.assertTrue(true)

    );
}

JUnit 에서 제공하는 Annotation

  • @Test
    메서드 위에 해당 Annotation 을 선언해, 테스트 대상 메서드임을 지정할 수 있습니다.###

  • @BeforeEach
    모든 @Test 메서드가 실행되기 전에 실행되는 메서드를 지정하는 Annotation 입니다.
    테스트 마다 공통으로 쓰이면서, 테스트 전에 초기화되어야 할 항목이 들어갑니다.

  • @AfterEach
    모든 @Test 메서드의 실행이 끝난 뒤에 실행되는 메서드를 지정하는 Annotation 입니다.
    각 테스트가 끝나고 각각 호출됩니다.

  • @BeforeAll
    해당 테스트 클래스가 실행될 때, 딱 한번만 수행되는 메서드를 지정하는 Annotation 입니다.
    ex) DB 연결

  • @AfterAll
    해당 테스트 클래스가 실행이 끝난 뒤에 딱 한번만 수행되는 메서드를 지정하는 Annotation 입니다.
    ex) DB 연결 해제

Spring 프로젝트 Test 코드

  • Spring 프로젝트 생성 시, src/test 폴더에 Test 코드를 작성하면 됩니다.
    기본적으로 메인 클래스 이름 + Tests 가 붙은 Test 용 클래스가 제공됩니다.

  • @SpringBootTest
    애플리케이션 전체 (스프링 컨테이너에 등록될 모든 비)를 로드하여 테스트 진행 합니다. - 통합 테스트

@SpringBootTest
class BlogApplicationTests {
	
    @Test // 테스트를 위한 메서드임을 명시해줍니다.
    void contextLoads() {
    }
    
}

Unit (단위) Test 코드 작성

repository 단위 테스트

Annotation

  • @DataJpaTest

    • JPA 관련 Bean 들만 메모리에 띄워줍니다.
    • 기본으로 내장된 메모리 데이터베이스를 사용하고,
      @AutoConfigureTestDatabase 를 사용하여 설정을 재 정의 할 수 있습니다.
  • @AutoConfigureTestDatabase

    • replace 속성을 활용하여 어떤 Database 로 테스트할지 결정 합니다.
    • Replace.ANY : 내장 DB (가짜 DB)로 테스트를 수행합니다. - 단위 테스트
      Replace.NONE : 실제 DB로 테스트를 수행합니다. - 통합테스트
  • @Transactional

    • 각각의 테스트 함수가 종료될 때 마다, Transactional 을 rollback 해줍니다.
      이로인해, 각 메서드의 독립적인 테스트를 보장해줍니다.
    • @DataJpaTest 내에 포함되어 있습니다. 즉, @DataJpaTest 를 사용시, 자동으로 적용됩니다.

Repository 단위 테스트 클래스 코드

test.java.gdsc.blog.unit.repository.PostRepositoryUnitTest.java

// @Transactional - @DataJpaTest 에 포함되어 있습니다.
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
@DataJpaTest
public class PostRepositoryUnitTest {

    @Autowired // 필요한 Bean 에 대하여 필드 주입 방식으로 의존성 주입
    private PostRepository postRepository;

    @Autowired
    private EntityManager entityManager;

    @BeforeEach
    public void init() {
        // 테이블 autoincrement 초기화 
        // @Transactional 로 트랜잭션이 롤백되어도, AutoIncrement로 설정된 숫자 정책은 초기화 되지 않음
        entityManager
               .createNativeQuery("ALTER TABLE post ALTER COLUMN id RESTART WITH 1")
               .executeUpdate();
    }

}

save_테스트 작성

...
public class PostRepositoryUnitTest {

    ...
    @Test
    public void save_테스트() {
       // given - 주어진 상황 (Post 데이터)
       Post post = new Post(null, "스프링부트 따라하기", "스프링부트 따라하기 내용");

       // when - 특정 행동 (Post 데이터 저장)
       Post postEntity = postRepository.save(post);

       // then - 결과 (저장 결과의 제목 확인)
       assertEquals("스프링부트 따라하기", postEntity.getTitle());
    }

}

saveAll_테스트 작성

...
public class PostRepositoryUnitTest {

    ...
    @Test
    public void saveAll_테스트() {
        // given
        List<Post> postList = Arrays.asList(
                new Post(1L, "스프링부트 따라하기", "스프링부트 따라하기 내용"),
                new Post(2L, "리엑트 따라하기", "리엑트 따라하기 내용")
        );
        // when
        List<Post> postEntityList = postRepository.saveAll(postList);

        // then
        assertArrayEquals(postEntityList.toArray(), postList.toArray());

    }
    ...
   
}

findById_테스트 작성

...
public class PostRepositoryUnitTest {

    ...
    @Test
    public void findById_테스트() {
        // given
        postRepository.saveAll(
                Arrays.asList(
                        new Post(1L, "스프링부트 따라하기", "스프링부트 따라하기 내용"),
                        new Post(2L, "리엑트 따라하기", "리엑트 따라하기 내용")
                )
        );
        Long id = 1L;

        // when
        Optional<Post> postEntity = postRepository.findById(id);

        // then
        assertTrue(postEntity.isPresent());
        assertEquals(1L, postEntity.get().getId());
        assertEquals("스프링부트 따라하기", postEntity.get().getTitle());

    }
    ...
   
}

findById_empty_테스트 작성

...
public class PostRepositoryUnitTest {

    ...
    @Test
    public void findById_empty_테스트() {
        // given
        Long id = 1L;

        // when
        Optional<Post> postEntity = postRepository.findById(id);

        // then
        assertTrue(postEntity.isEmpty());
    }
    ...
   
}

findAll_테스트 작성

...
public class PostRepositoryUnitTest {

    ...
    @Test
    public void findAll_테스트() {
        // given
        postRepository.saveAll(
                Arrays.asList(
                        new Post(null, "스프링부트 따라하기", "스프링부트 따라하기 내용"),
                        new Post(null, "리엑트 따라하기", "리엑트 따라하기 내용")
                )
        );

        // when
        List<Post> postEntityList = postRepository.findAll();

        // then
        assertNotEquals(0, postEntityList.size());
        assertEquals(2, postEntityList.size());
    }
    ...
   
}

deleteById_테스트 작성

...
public class PostRepositoryUnitTest {

    ...
    public void deleteById_테스트() {
        // given
        postRepository.saveAll(
                Arrays.asList(
                        new Post(1L, "스프링부트 따라하기", "스프링부트 따라하기 내용"),
                        new Post(2L, "리엑트 따라하기", "리엑트 따라하기 내용")
                )
        );
        Long id = 1L;


        // when
        postRepository.deleteById(id);
        Optional<Post> postEntity = postRepository.findById(id);

        // then
        assertTrue(postEntity.isEmpty());
    }
    ...
   
}

Service 단위 테스트

Mockito 환경 활용

  • 단위 테스트를 위해 Mock(단위 테스트를 위한 가짜 객체)을 만들어주는 프레임워크 입니다.

Mockito 환경을 사용하는 이유

  • Service 와 해당 Service 내에 필요한 Repository, 단지 2개의 객체만 필요하기 때문 입니다. (독립성 보장)
  • 실제 객체를 만들기에는 비용과 시간이 많이 들기 때문입니다.
  • 의존성이 복잡하게 맺어져있는 경우, 테스트 시 Mock 을 활용하여 단위 테스트의 독립성을 보장하기 위해서 입니다.

Annotation

  • @ExtendWith(MockitoExtension.class)
    클래스 상단에 추가하여, 해당 JUnit 테스트 클래스가 Mockito 환경으로 실행되는 것 을 명시합니다.

  • @Mock
    단위 테스트를 위한 가짜 객체를 생성하여 Mock 환경에 띄워줍니다.

  • @InjectMocks
    @Mock 또는 @Spy 로 생성된 Mock 객체들의 의존성을 주입합니다.
    @InjectMocks을 쓴 객체의 Mock 객체를 주입 받을 수 있는 형태 : 생성자, 수정자, 필드 주입 방식

stub

  • Mock 객체가 실제로 동작하는 것 처럼 보이도록, 특정 메서드 호출에 대해 미리 반환값을 정해놓은 것 입니다.

Service 단위 테스트 클래스 코드

test.java.gdsc.blog.unit.service.PostServiceUnitTest.java

@ExtendWith(MockitoExtension.class) // Mockito 환경으로 실행
public class PostServiceUnitTest {

    @InjectMocks // (PostService 객체가 만들어질때) 해당 파일에 @Mock로 등록된 모든 애들을 주입 받는다.
    private PostService postService;

    // PostRepository => 가짜 객체로 만들 수 있음 - Mockito 환경에서 이를 제공
    @Mock
    private PostRepository postRepository;

}

save_테스트 작성

@ExtendWith(MockitoExtension.class)
public class PostServiceUnitTest {
    ...
    @Test
    public void save_테스트() {

       // given
       Post post = new Post();
       post.setTitle("스프링부트 따라하기");
       post.setContent("스프링부트 따라하기 내용");

       // stub - 동작 지정
       when(postRepository.save(post)).thenReturn(post);

       // test execute
       Post postEntity = postService.save(WritePostReq.builder()
               .title("스프링부트 따라하기")
               .content("스프링부트 따라하기 내용")
               .build());

       // then
       assertEquals(post, postEntity); // expected , actual
    }
}

findById_테스트 작성

@ExtendWith(MockitoExtension.class)
public class PostServiceUnitTest {
    ...
    @Test
    public void findById_테스트() {
        // given
        Long id = 1L;
        Post post = new Post();
        post.setId(id);
        post.setTitle("스프링부트 따라하기");
        post.setContent("스프링부트 따라하기 내용");

        // stub - 동작 지정
        when(postRepository.findById(id)).thenReturn(java.util.Optional.of(post));

        // when
        Post postEntity = postService.findById(id);

        // then
        assertEquals(post, postEntity);
    }
}

findById_fail_테스트 작성

@ExtendWith(MockitoExtension.class)
public class PostServiceUnitTest {
    ...
    @Test
    public void findById_fail_테스트() {
        // given
        Long id = 1L;

        // stub - 동작 지정
        when(postRepository.findById(id)).thenReturn(Optional.empty());

        // when & then
        // assertThrows 에서 해당 실행 부분이 expected Exception 을 throw 하는지 확인
        Exception exception = assertThrows(NoSuchElementException.class, () -> {
            postService.findById(id);
        });

        assertEquals("id를 확인해주세요!!", exception.getMessage());

    }
}

findAll_테스트 작성

@ExtendWith(MockitoExtension.class)
public class PostServiceUnitTest {
    ...
    @Test
    public void findAll_테스트() {
        // given
        List<Post> postList = new ArrayList<>();
        postList.add(new Post(1L, "스프링부트 따라하기", "스프링부트 따라하기 내용"));
        postList.add(new Post(2L, "리액트 따라하기", "리액트 따라하기 내용"));

        // stub - 동작 지정
        when(postRepository.findAll()).thenReturn(postList);

        // when
        List<Post> postEntityList = postService.findAll();

        // then
        assertEquals(postList, postEntityList);
    }
}

updateById_테스트 작성

@ExtendWith(MockitoExtension.class)
public class PostServiceUnitTest {
    ...
    @Test
    public void updateById_테스트() {
        // given
        Long id = 1L;

        Post post = new Post();
        post.setId(id);
        post.setTitle("스프링부트 따라하기");
        post.setContent("스프링부트 따라하기 내용");

        WritePostReq writePostReq = WritePostReq.builder()
                .title("스프링부트 또 따라하기")
                .content("스프링부트 또 따라하기 내용").build();

        // stub - 동작 지정
        when(postRepository.findById(1L)).thenReturn(java.util.Optional.of(post));

        // when
        Post postEntity = postService.updateById(id, writePostReq);

        // then
        assertEquals("스프링부트 또 따라하기", postEntity.getTitle());
        assertEquals("스프링부트 또 따라하기 내용", postEntity.getContent());

    }
}

Controller 단위 테스트

Annotation

  • @WebMvcTest
    Web Layer 를 테스트 하고 싶을 때 사용한다. 즉, Controller 관련 로직만 메모리에 띄웁니다.

  • @ExtendWith(SpringExtension.class)
    클래스 상단에 추가하여, 해당 JUnit 테스트 클래스가 Spring 환경으로 실행되는 것 을 명시 합니다.

  • @MockBean
    단위 테스트를 위한 가짜 객체를 생성하여 Spring Container 에 빈으로 등록시켜줍니다.

Controller 단위 테스트 클래스 코드

test.java.gdsc.blog.unit.controller.PostControllerUnitTest.java

// -> @ExtendWith(SpringExtension.class) - 스프링 환경 확장시 사용하는 애노테이션 
// - Spring 에서 JUnit5 에서 테스트 할때 필수
@WebMvcTest // @ExtendWith(SpringExtenstion.class) 를 포함
public class PostControllerUnitTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean // IoC 환경에 해당 가짜 빈이 등록된다. 
    // - Spring 환격을 사용하였기 때문에, 빈으로 등록해주어야 한다.
    private PostService postService;

}

save_테스트 작성

@WebMvcTest 
public class PostControllerUnitTest {
    ...
    @Test // 테스트 명시
    public void save_테스트() throws Exception {
       // given (테스트를 하기 위한 준비)
       WritePostReq writePostReq = WritePostReq.builder()
               .title("스프링부트 따라하기")
               .content("스프링부트 따라하기 내용")
               .build();
       // Object 를 JSON 으로 변경해주는 함수
       String content = new ObjectMapper().writeValueAsString(writePostReq);

       // stub (미리 행동을 지정함) - postService 는 가짜 이기 때문에 제대로 실행되지 않기 때문에 - controller 만 신경쓰기 때문에 가능
       when(postService.저장하기(writePostReq)).thenReturn(new Post(1L, "스프링부트 따라하기", "스프링부트 따라하기 내용"));

       // when (테스트 실행)
       ResultActions resultAction = mockMvc.perform(post("/post")
               .contentType(MediaType.APPLICATION_JSON)
               .content(content)
               .accept(MediaType.APPLICATION_JSON));

       // then (검증)
       resultAction
               .andExpect(status().isCreated())
               .andExpect(jsonPath("$.title").value("스프링부트 따라하기")) // jsonPath : json 에서 변수로 결과 받아옴
               .andExpect(jsonPath("$.content").value("스프링부트 따라하기 내용")) // $ 는 전체를 뜻함, . 은 구분자
               .andDo(MockMvcResultHandlers.print()); // 결과 출력
    }
}

findAll_테스트 작성

@WebMvcTest 
public class PostControllerUnitTest {
    ...
    @Test
    public void findAll_테스트() throws Exception{
        // given
        // stub 생성
        List<Post> postList = new ArrayList<>();
        postList.add(new Post(1L, "스프링부트 따라하기", "스프링부트 따라하기 내용"));
        postList.add(new Post(2L, "리액트 따라하기", "리액트 따라하기 내용"));

        when(postService.모두가져오기()).thenReturn(postList);

        // when
        ResultActions resultActions = mockMvc.perform(get("/post")
                .accept(MediaType.APPLICATION_JSON));

        // then
        resultActions
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", Matchers.hasSize(2)))
                .andExpect(jsonPath("$.[0].title").value("스프링부트 따라하기"))
                .andDo(MockMvcResultHandlers.print());
    }
}

findById_테스트 작성

@WebMvcTest 
public class PostControllerUnitTest {
    ...
    @Test
    public void findById_테스트() throws Exception{
        // given
        Long id = 1L;

        // stub 생성
        when(postService.한건가져오기(id)).thenReturn(new Post(1L, "스프링부트 따라하기", "스프링부트 따라하기 내용"));


        // when
        ResultActions resultAction = mockMvc.perform(get("/post/{id}", id)
                .accept(MediaType.APPLICATION_JSON));

        // then
        resultAction
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.title").value("스프링부트 따라하기"))
                .andDo(MockMvcResultHandlers.print());

    }
}

findById_fail_테스트 작성

@WebMvcTest 
public class PostControllerUnitTest {
    ...
    @Test
    public void findById_fail_테스트() throws Exception {
        // given
        Long id = 1L;

        when(postService.한건가져오기(id)).thenThrow(new NoSuchElementException("id를 확인해주세요!!"));

        // when
        ResultActions resultActions = mockMvc.perform(get("/post/{id}", id)
                .accept(MediaType.APPLICATION_JSON));

        // then
        resultActions
                .andExpect(status().is4xxClientError())
                .andExpect(jsonPath("$.code").value("Item Not Found"))
                .andExpect(jsonPath("$.message").value("id를 확인해주세요!!"))
                .andDo(MockMvcResultHandlers.print());
    }
}

updateById_테스트 작성

@WebMvcTest 
public class PostControllerUnitTest {
    ...
    @Test
    public void updateById_테스트() throws Exception {
        // given
        Long id = 1L;
        WritePostReq writePostReq = WritePostReq.builder()
                .title("스프링부트 또 따라하기")
                .content("스프링부트 또 따라하기 내용")
                .build();
        String content = new ObjectMapper().writeValueAsString(writePostReq);

        // postService 는 가짜로 올라가있는 것 이기 때문에 실제 실행되는 것은 아님
        when(postService.수정하기(id, writePostReq)).thenReturn(new Post(1L, "스프링부트 또 따라하기", "스프링부트 또 따라하기 내용"));

        // when
        ResultActions resultActions = mockMvc.perform(put("/post/{id}", id)
                .contentType(MediaType.APPLICATION_JSON)
                .content(content)
                .accept(MediaType.APPLICATION_JSON));

        // then
        resultActions
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.title").value("스프링부트 또 따라하기"))
                .andDo(MockMvcResultHandlers.print());
    }
}

deleteById_테스트 작성

@WebMvcTest 
public class PostControllerUnitTest {
    ...
    @Test
    public void delete_테스트() throws Exception {
        // given
        Long id = 1L;

        // postService 는 가짜로 올라가있는 것 이기 때문에 실제 실행되는 것은 아님
        when(postService.삭제하기(id)).thenReturn("ok");

        // when
        ResultActions resultActions = mockMvc.perform(delete("/post/{id}", id)
                .accept(MediaType.TEXT_PLAIN));

        // then - JSON 응답 시
        resultActions
                .andExpect(status().isOk())
                .andDo(MockMvcResultHandlers.print());

        // 문자(String) 응답 시
        MvcResult requestResult = resultActions.andReturn();
        String result = requestResult.getResponse().getContentAsString();
        assertEquals("ok", result);
    }
}

Integration (통합) Test 코드 작성

Annotation

  • @SpringBooteTest - webEnvironment 속성
    MOCK (default) : 실제 톰캣(WAS)으로 올리는 것이 아니라, 다른 톰캣으로 테스트 - 모의 환경 제공
    RANDOM_PORT : 실제 톰캣으로 테스트 - 실제 웹 환경 제공
  • @AutoConfitureMockMvc
    Controller, Service, Repository 에 관련된 로직들을 메모리에 띄워준다. - 통합 테스트에 적합
  • @Transactional
    각각의 테스트 함수가 종료될 때마다, Transaction 을 rollback 해줌 - 각 메서드의 독립적인 테스트를 보장

통합 테스트 코드 작성

통합 테스트 클래스 코드

test.java.gdsc.blog.integration.PostIntegration.java

@Transactional
@AutoConfigureMockMvc 
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
// -> @ExtendWith(SpringExtension.class) 
// - 스프링 환경 확장시 사용하는 애노테이션 - Spring 에서 JUnit5 에서 테스트 할때 필수
public class PostControllerIntegreTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private PostRepository postRepository;

    @Autowired
    private EntityManager entityManager;

    @BeforeEach // 모든 테스트 이전에 실행 됨
    public void init() {
        entityManager
                .createNativeQuery("ALTER TABLE post ALTER COLUMN id RESTART WITH 1")
                .executeUpdate();
    }
}

save_테스트 작성

...
public class PostControllerIntegreTest {
    ...
    @Test // 테스트 명시
    public void save_테스트() throws Exception {
       // given (테스트를 하기 위한 준비)
       WritePostReq writePostReq = WritePostReq.builder()
               .title("스프링부트 따라하기")
               .content("스프링부트 따라하기 내용")
               .build();
       // Object 를 JSON 으로 변경해주는 함수
       String content = new ObjectMapper().writeValueAsString(writePostReq);

       // 실제 postService 가 Bean 으로 등록 되어 있기 때문에, stub 이 필요 없음

       // when (테스트 실행)
       ResultActions resultAction = mockMvc.perform(post("/post")
               .contentType(MediaType.APPLICATION_JSON)
               .content(content)
               .accept(MediaType.APPLICATION_JSON));
       // then (검증)
       resultAction
               .andExpect(status().isCreated())
               .andExpect(jsonPath("$.id").value(1L))
               .andExpect(jsonPath("$.title").value("스프링부트 따라하기")) 
               // jsonPath : json 에서 변수로 결과 받아옴
               .andExpect(jsonPath("$.content").value("스프링부트 따라하기 내용")) 
               // $ 는 전체를 뜻함, . 은 구분자
               .andDo(MockMvcResultHandlers.print()); // 결과 출력

    }
}

findAll_테스트 작성

...
public class PostControllerIntegreTest {
    ...
    @Test
    public void findAll_테스트() throws Exception{
        // given
        // data 생성
        List<Post> postList = new ArrayList<>();
        postList.add(new Post(null, "스프링부트 따라하기", "스프링부트 따라하기 내용"));
        postList.add(new Post(null, "리액트 따라하기", "리액트 따라하기 내용"));

        postRepository.saveAll(postList);

        // when
        ResultActions resultActions = mockMvc.perform(get("/post")
                .accept(MediaType.APPLICATION_JSON));

        // then
        resultActions
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", Matchers.hasSize(2)))
                .andExpect(jsonPath("$.[0].id").value(1L))
                .andExpect(jsonPath("$.[0].title").value("스프링부트 따라하기"))
                .andDo(MockMvcResultHandlers.print());
    }
}

findById_테스트 작성

...
public class PostControllerIntegreTest {
    ...
    @Test
    public void findById_테스트() throws Exception{
        // given
        // data 생성
        List<Post> postList = new ArrayList<>();
        postList.add(new Post(null, "스프링부트 따라하기", "스프링부트 따라하기 내용"));
        postList.add(new Post(null, "리액트 따라하기", "리액트 따라하기 내용"));

        postRepository.saveAll(postList);

        Long id = 1L;


        // when
        ResultActions resultAction = mockMvc.perform(get("/post/{id}", id)
                .accept(MediaType.APPLICATION_JSON));

        // then
        resultAction
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.title").value("스프링부트 따라하기"))
                .andDo(MockMvcResultHandlers.print());

    }
}

findById_fail_테스트 작성

...
public class PostControllerIntegreTest {
    ...
    @Test
    public void findById_fail_테스트() throws Exception {
        // given
        Long id = 1L;

        // when
        ResultActions resultAction = mockMvc.perform(get("/post/{id}", id)
                .accept(MediaType.APPLICATION_JSON));

        // then
        resultAction
                .andExpect(status().is4xxClientError())
                .andExpect(jsonPath("$.code").value("Item Not Found"))
                .andExpect(jsonPath("$.message").value("id를 확인해주세요!!"))
                .andDo(MockMvcResultHandlers.print());
    }
}

updateById_테스트 작성

...
public class PostControllerIntegreTest {
    ...
    @Test
    public void updateById_테스트() throws Exception {
        // given
        // data 생성
        List<Post> postList = new ArrayList<>();
        postList.add(new Post(null, "스프링부트 따라하기", "스프링부트 따라하기 내용"));
        postList.add(new Post(null, "리액트 따라하기", "리액트 따라하기 내용"));

        postRepository.saveAll(postList);

        Long id = 1L;
        WritePostReq writePostReq = WritePostReq.builder()
                .title("스프링부트 또 따라하기")
                .content("스프링부트 또 따라하기 내용")
                .build();
        String content = new ObjectMapper().writeValueAsString(writePostReq);

        // when
        ResultActions resultActions = mockMvc.perform(put("/post/{id}", id)
                .contentType(MediaType.APPLICATION_JSON)
                .content(content)
                .accept(MediaType.APPLICATION_JSON));

        // then
        resultActions
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.title").value("스프링부트 또 따라하기"))
                .andDo(MockMvcResultHandlers.print());
    }
}

deleteById_테스트 작성

...
public class PostControllerIntegreTest {
    ...
    @Test
    public void deleteById_테스트() throws Exception {
        // given
        // data 생성
        List<Post> postList = new ArrayList<>();
        postList.add(new Post(null, "스프링부트 따라하기", "스프링부트 따라하기 내용"));
        postList.add(new Post(null, "리액트 따라하기", "리액트 따라하기 내용"));

        postRepository.saveAll(postList);

        Long id = 1L;

        // when
        ResultActions resultAction = mockMvc.perform(delete("/post/{id}", id));

        // then
        resultAction.andExpect(status().isOk()).andDo(MockMvcResultHandlers.print());

        MvcResult requestResult = resultAction.andReturn();
        String result = requestResult.getResponse().getContentAsString();
        assertEquals("ok", result);
    }
}
profile
https://github.com/yuseogi0218

0개의 댓글