스프링 프로젝트 테스트 방법

정미·2022년 5월 17일
0

유닛 테스트를 작성할 때에는 @SpringBootTest로 전체 스프링 부트를 띄우게끔 하지 않고 가볍게 테스트하려고 이것저것 찾아보면서 적용해보았다.

Repository 단위 테스트

@DataJpaTest + @TestEntityManager

Spring data Jpa를 테스트할 때 사용
인메모리 db를 생성해서 entity들만 스캔, 테스트 후 자동 롤백

@DataJpaTest
class ProductOptionRepositoryTest {

    @Autowired
    private ProductOptionRepository productOptionRepository;

    @Autowired
    private TestEntityManager entityManager;

    private List<String> option1s, option2s;
    private Product product;
    private List<ProductOption> productOptions;

    @BeforeEach
    void setUp() {
        option1s = new ArrayList<>() {{
            add("블랙");
            add("네이비");
        }};
        option2s = new ArrayList<>() {{
            add("M");
            add("L");
            add("XL");
        }};
        product = new Product("후드티");
        productOptions = new ArrayList<>() {
            {
                add(new ProductOption(product, 10, option1s.get(0), option2s.get(0)));
                add(new ProductOption(product, 20, option1s.get(0), option2s.get(1)));
                add(new ProductOption(product, 30, option1s.get(0), option2s.get(2)));
                add(new ProductOption(product, 40, option1s.get(1), option2s.get(1)));
                add(new ProductOption(product, 50, option1s.get(1), option2s.get(2)));
            }
        };

        entityManager.persistAndFlush(product);
        productOptions.forEach(productOption -> entityManager.persistAndFlush(productOption));
    }

    @DisplayName("제품의 옵션1로 모든 옵션 조회")
    @Test
    void findAllByProductAndOption1() {
        // when
        List<ProductOption> all = productOptionRepository.findAllByProductAndOption1(product, option1s.get(1));

        // then
        assertAll(
                () -> assertThat(all).hasSize(2),
                () -> assertThat(all).extracting("option2")
                        .containsOnly(option2s.get(1), option2s.get(2))
        );
    }
}

Service 단위 테스트

@ExtendWith(MockitoExtension.class) + @Mock + @InjectMocks

@Mock or Mockito.mock(A.class): mocking할 repository
@Injectmocks: 모킹한 객체를 사용할 객체 - service

@ExtendWith(MockitoExtension.class)
class ProductOptionServiceMockingTest {

    @Mock
    private ProductOptionRepository productOptionRepository;

    @Mock
    private ProductRepository productRepository;

    @InjectMocks
    private ProductOptionService productOptionService;

    private Product product;
    private Long productId;

    private ProductOption productOption;
    private int stock;
    private String option1;
    private String option2;

    @BeforeEach
    void setUp() {
        product = new Product("바지");
        productId = 1L;

        stock = 10;
        option1 = "블랙";
        option2 = "M";
        productOption = ProductOption.builder()
                .product(product)
                .stock(stock)
                .option1(option1)
                .option2(option2)
                .build();
        productOption.setCreatedAt(LocalDateTime.now());
        productOption.setUpdatedAt(LocalDateTime.now());

        Mockito.lenient().when(productRepository.findById(productId))
                .thenReturn(Optional.of(product));
    }

    @Test
    @DisplayName("제품 옵션 구매시 재고 부족 예외")
    void purchaseProductOption() {
        // given
        int purchasedNum = 15;
        when(productOptionRepository.findById(any()))
                .thenReturn(Optional.of(productOption));

        // when, then
        assertThatThrownBy(() -> productOptionService.purchase(anyLong(), purchasedNum))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("재고가 부족합니다. product option ")
                .hasMessageContaining("의 재고: " + stock);
    }
}

Controller 통합 테스트

@WebMvcTest(ProductOptionController.class) + MockMvc + RestDocs

Web 관련된 설정과 클래스들만 컨텍스트로 띄워주는 기능

@WebMvcTest(ProductOptionController.class)
class ProductOptionControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private ProductOptionService productOptionService;

    private Long productId;
    private ProductSimpleDto productDto;
    private Long productOptionId;
    private ProductOptionResponse productOptionResponse;

    @BeforeEach
    void setUp() {
        productId = 1L;
        productDto = ProductSimpleDto.builder()
                .id(productId)
                .name("바지")
                .price(1000)
                .status("판매 중")
                .build();
        productOptionId = 1L;
        productOptionResponse = ProductOptionResponse.builder()
                .id(productOptionId)
                .productDto(productDto)
                .stock(20)
                .option1("흑청")
                .option2("30")
                .createdAt(LocalDateTime.now().toString())
                .updatedAt(LocalDateTime.now().toString())
                .build();
    }

    @Test
    @DisplayName("제품 옵션 생성")
    void createProductOption() throws Exception {
        // given
        ProductOptionRequest productOptionRequest = ProductOptionRequest.builder()
                .productDto(productDto)
                .stock(20)
                .option1("흑청")
                .option2("30")
                .build();
        String requestBody = objectMapper.writeValueAsString(productOptionRequest);

        given(productOptionService.create(anyLong(), any()))
                .willReturn(productOptionResponse);

        // when
        ResultActions resultActions = mockMvc.perform(post("/api/v1/products/{productId}/productOptions", productId)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(requestBody))
                .andDo(print());

        // then
        resultActions
                .andExpect(status().isCreated())
                .andExpect(header().string(
                        LOCATION, "/api/v1/products/" + productId + "/productOptions/" + productOptionResponse.getId()))
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
                .andExpect(jsonPath("id").value(productOptionId))
                .andExpect(jsonPath("productDto").exists())
                .andExpect(jsonPath("productDto.id").value(productId))
                .andExpect(jsonPath("productDto.name").value("바지"))
                .andExpect(jsonPath("stock").value(20))
                .andExpect(jsonPath("option1").value("흑청"))
                .andExpect(jsonPath("option2").value("30"))
                .andDo(print());
    }
}

시스템 테스트

@SpringBootTest + TestRestTemplate

전체 컨텍스트를 띄워서 클라이언트에서 요청을 보내듯이 테스트해볼 수 있음

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ProductOptionTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private ProductOptionRepository productOptionRepository;

    @Autowired
    private ProductRepository productRepository;

    private Long productId;
    private Product savedProduct;
    private String url;

    @BeforeEach
    void setUp() {
        restTemplate.getRestTemplate().setRequestFactory(new HttpComponentsClientHttpRequestFactory());

        productOptionRepository.deleteAll();
        productRepository.deleteAll();

        Product product = Product.builder()
                .name("청바지")
                .price(1000)
                .category("하의")
                .build();
        savedProduct = productRepository.saveAndFlush(product);
        productId = savedProduct.getId();
        url = "/api/v1/products/" + productId + "/productOptions";
    }

    @Test
    @DisplayName("옵션명 변경")
    void updateOptionName() {
        // given
        ProductOption saved = saveProductOption(10, "블랙");

        ProductOptionNameRequest request = new ProductOptionNameRequest("네이비", "L");
        HttpEntity<ProductOptionNameRequest> requestEntity = new HttpEntity<>(request);

        // when
        ResponseEntity<ProductOptionResponse> response = restTemplate.exchange(
                url + "/" + saved.getId() + "/optionName",
                HttpMethod.PATCH,
                requestEntity,
                ProductOptionResponse.class);

        // then
        assertAll(
                () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
                () -> assertThat(response.getBody()).isNotNull(),
                () -> assertThat(response.getBody()).hasFieldOrPropertyWithValue("option1", "네이비"),
                () -> assertThat(response.getBody()).hasFieldOrPropertyWithValue("option2", "L")
        );
    }

    private ProductOption saveProductOption(int stock, String option1) {
        ProductOption productOption = ProductOption.builder()
                .product(savedProduct)
                .stock(stock)
                .option1(option1)
                .build();
        return productOptionRepository.saveAndFlush(productOption);
    }
}

참고 자료

코드 원본

0개의 댓글