2022-08-09~10 개발일지

컴클로딩·2022년 8월 10일
0

📍 Intro

  • 어제부터 TDD를 진행하는데 쉽지않다...일단 다른 조 상황을 보니 통합테스트 코드만 짰길래 나도 우선적으로 통합테스트 코드를 작성 후에 "망나니 개발자"님의 "[Spring] TDD로 멤버십 등록 API 구현 예제 - (3/5)" 포스팅을 보면서 진행중이다.

1. Repository Test 코드 작성하다가 발생한 오류

  • 발생한 오류

    • Failed to load ApplicationContext
      java.lang.IllegalStateException: Failed to load ApplicationContext
      ...
      Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'productRepositoryImpl' defined in file [C:\Users\21528463\IdeaProjects\mocosa\build\classes\java\main\com\hanghae99\mocosa\layer\repository\ProductRepositoryImpl.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.querydsl.jpa.impl.JPAQueryFactory' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
      ...
      Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.querydsl.jpa.impl.JPAQueryFactory' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
      ...
  • 오류가 발생한 코드

@RequiredArgsConstructor
public class ProductRepositoryImpl implements ProductRepositoryCustom {

    private final JPAQueryFactory queryFactory;
}
  • 오류 발생 이유
    • 사실 정확하게 이유를 모르겠지만 오류창을 보고 대충(?) ProductRepositoryImpl의 JPAQueryFactory 때문이라는 정도만 알겠다.
  • 해결책
    • private final JPAQueryFactory queryFactory; 지워보자!
  • 오류 해결 후 코드
@RequiredArgsConstructor
public class ProductRepositoryImpl implements ProductRepositoryCustom {

}

2.

  • 발생한 오류
    • java.lang.IllegalStateException: No primary or single unique constructor found for interface java.awt.print.Pageable
  • 오류가 발생한 코드
    @GetMapping("/api/search")
    @ResponseBody
    public Slice<SearchResponseDto> searchProduct(Pageable pageable,
                                                  @RequestParam(required = false, defaultValue = "리뷰순") String sort,
                                                  @RequestParam(required = false, defaultValue = "전체") String categoryFilter,
                                                  @RequestParam(required = false, defaultValue = "0") int minPriceFilter,
                                                  @RequestParam(required = false, defaultValue = "2147483647") int maxPriceFilter,
                                                  @RequestParam(required = false, defaultValue = "0") int reviewFilter,
                                                  @RequestParam String keyword) {
        return productService.searchProduct(pageable, sort, categoryFilter, minPriceFilter, maxPriceFilter, reviewFilter);
    }
  • 오류 발생 이유
    • sort가 "리뷰순"이라서?
  • 해결책
    • Pageable대신 그냥 page만 받아서 내부적으로 처리하기
  • 오류 해결 후 코드
    @GetMapping("/api/search")
    @ResponseBody
    public Slice<SearchResponseDto> searchProduct(@RequestParam(required = false, defaultValue = "0") int page,
                                                  @RequestParam(required = false, defaultValue = "리뷰순") String sort,
                                                  @RequestParam(required = false, defaultValue = "전체") String categoryFilter,
                                                  @RequestParam(required = false, defaultValue = "0") int minPriceFilter,
                                                  @RequestParam(required = false, defaultValue = "2147483647") int maxPriceFilter,
                                                  @RequestParam(required = false, defaultValue = "0") int reviewFilter,
                                                  @RequestParam String keyword) {
        return productService.searchProduct(page, sort, categoryFilter, minPriceFilter, maxPriceFilter, reviewFilter);
    }

3. 통합테스트에서 실제 서버 구동한 후 결과와 다르게 <401 UNAUTHORIZED> 에러 발생

  • 발생한 오류
    • expected: <400 BAD_REQUEST> but was: <401 UNAUTHORIZED>
  • 오류 발생 이유
    • Spring Security를 프로젝트 시작할 때 gradle에 추가해줘서 발생하는 오류 같다.
  • 해결책
    • gradle에서 Spring Security관련
  • 오류 해결 후 코드
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
//    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'mysql:mysql-connector-java'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'

    // 테스트 코드를 위한 Lombok 라이브러리
    testCompileOnly 'org.projectlombok:lombok:1.18.12'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.12'

    // 3. querydsl dependencies 추가
    implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
    implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
}

4. Type definition error

  • 발생한 오류
    • Type definition error: [simple type, class com.hanghae99.mocosa.integration.ProductIntegrationTest$SearchResponseDtos]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.hanghae99.mocosa.integration.ProductIntegrationTest$SearchResponseDtos` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
  • 오류 발생 이유
    • 콘솔 창을 보고 잘 이해는 안갔지만 해결해나가는 과정에서 에러 코드의 의미가 Dto 설정 문제였던 것 같음.
  • 해결책
    • @Setter를 추가해준다.
  • 오류 해결 후 코드
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
public class ProductIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    @DisplayName("검색에 성공한 케이스 - 필터 제외")
    public void case1(){
        //given
        String keyword = "무탠다드";

        //when
        ResponseEntity<SearchResponseDtoList> response = restTemplate
                .getForEntity(
                        "/api/search?keyword="+keyword,
                        SearchResponseDtoList.class
                );

        //then
        assertEquals(HttpStatus.OK, response.getStatusCode());
        SearchResponseDtoList responseBody = response.getBody();
        assertNotNull(responseBody);

        assertEquals(
                1L
                ,responseBody.content.get(0).productId);
        assertEquals(
                "릴렉스 핏 크루 넥 반팔 티셔츠"
                ,responseBody.content.get(0).name);
        assertEquals(
                "image.png"
                ,responseBody.content.get(0).thumbnail);
        assertEquals(
                "무신사 스탠다드"
                ,responseBody.content.get(0).brandName);
        assertEquals(
                "상의"
                ,responseBody.content.get(0).category);
        assertEquals(
                10690
                ,responseBody.content.get(0).price);
        assertEquals(
                100
                ,responseBody.content.get(0).amount);
        assertEquals(
                69058
                ,responseBody.content.get(0).reviewNum);
        assertEquals(
                4.8
                ,responseBody.content.get(0).reviewAvg);
    }

    @Getter
    @Setter
    @Builder
    static class SearchResponseDtoList {
        private List<SearchResponseDto> content;
    }


    @Getter
    @Setter
    @Builder
    static class SearchResponseDto{
        private Long productId;
        private String name;
        private String thumbnail;
        private String brandName;
        private String category;
        private int price;
        private int amount;
        private int reviewNum;
        private float reviewAvg;
    }

}
  • 오류 해결 후 코드
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
public class ProductIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    @DisplayName("검색에 성공한 케이스 - 필터 제외")
    public void case1(){
        //given
        String keyword = "무탠다드";

        //when
        ResponseEntity<SearchResponseDtoList> response = restTemplate
                .getForEntity(
                        "/api/search?keyword=" + keyword,
                        SearchResponseDtoList.class
                );

        //then
        assertEquals(HttpStatus.OK, response.getStatusCode());
        List<SearchResponseDto> responseBody = response.getBody().content;
        assertNotNull(responseBody);
        
        assertEquals(
                1L
                ,responseBody.content.get(0).productId);
        assertEquals(
                "릴렉스 핏 크루 넥 반팔 티셔츠"
                ,responseBody.content.get(0).name);
        assertEquals(
                "image.png"
                ,responseBody.content.get(0).thumbnail);
        assertEquals(
                "무신사 스탠다드"
                ,responseBody.content.get(0).brandName);
        assertEquals(
                "상의"
                ,responseBody.content.get(0).category);
        assertEquals(
                10690
                ,responseBody.content.get(0).price);
        assertEquals(
                100
                ,responseBody.content.get(0).amount);
        assertEquals(
                69058
                ,responseBody.content.get(0).reviewNum);
        assertEquals(
                4.8
                ,responseBody.content.get(0).reviewAvg);
    }

    @Getter
    @Setter
    @Builder
    static class SearchResponseDtoList {
        private List<SearchResponseDto> content;
    }


    @Getter
    @Setter
    @Builder
    static class SearchResponseDto{
        private Long productId;
        private String name;
        private String thumbnail;
        private String brandName;
        private String category;
        private int price;
        private int amount;
        private int reviewNum;
        private float reviewAvg;
    }

}
  • 다른 부분이 //then 아래 2번째줄이다. 오류코드는 SearchResponseDtoList responseBody = response.getBody();이고 해결한 코드는 List<SearchResponseDto> responseBody = response.getBody().content; 이렇게 반환 타입을 잘못 작성해서 난 오류였다. 다음부터는 반환타입을 잘 생각해서 코드를 작성하도록 하자!
  • 오류를 해결하면서 참고한 자료
    • 팀원의 다른 코드

5. 통합테스트에서 부동소수점 문제

  • 발생한 오류
    • org.opentest4j.AssertionFailedError:
      Expected :4.8
      Actual : 4.800000190734863
  • 오류가 발생한 코드
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
public class ProductIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    @DisplayName("검색에 성공한 케이스 - 필터 제외")
    public void case1(){
        //given
        String keyword = "무탠다드";

        //when
        ResponseEntity<SearchResponseDtoList> response = restTemplate
                .getForEntity(
                        "/api/search?keyword=" + keyword,
                        SearchResponseDtoList.class
                );

        //then
        assertEquals(HttpStatus.OK, response.getStatusCode());
        List<SearchResponseDto> responseBody = response.getBody().content;
        assertNotNull(responseBody);
        
        assertEquals(
                1L
                ,responseBody.content.get(0).productId);
        assertEquals(
                "릴렉스 핏 크루 넥 반팔 티셔츠"
                ,responseBody.content.get(0).name);
        assertEquals(
                "image.png"
                ,responseBody.content.get(0).thumbnail);
        assertEquals(
                "무신사 스탠다드"
                ,responseBody.content.get(0).brandName);
        assertEquals(
                "상의"
                ,responseBody.content.get(0).category);
        assertEquals(
                10690
                ,responseBody.content.get(0).price);
        assertEquals(
                100
                ,responseBody.content.get(0).amount);
        assertEquals(
                69058
                ,responseBody.content.get(0).reviewNum);
        assertEquals(
                4.8
                ,responseBody.content.get(0).reviewAvg);
    }
}
  • 오류 발생 이유
    • 기대값은 4.8인데 response값이 4.800000190734863이라서 부동소수점 문제를 가지고 있음.
  • 해결책
    • 테스트진행 할 때 TOLERRANCE 상수를 둬서 assertEquals에 해당 상수를 추가해주면 테스트에 통과할 수 있다.
  • 오류 해결 후 코드
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
public class ProductIntegrationTest {

	static final double TOLERANCE = 0.001;

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    @DisplayName("검색에 성공한 케이스 - 필터 제외")
    public void case1(){
        //given
        String keyword = "무탠다드";

        //when
        ResponseEntity<SearchResponseDtoList> response = restTemplate
                .getForEntity(
                        "/api/search?keyword=" + keyword,
                        SearchResponseDtoList.class
                );

        //then
        assertEquals(HttpStatus.OK, response.getStatusCode());
        List<SearchResponseDto> responseBody = response.getBody().content;
        assertNotNull(responseBody);
        
        assertEquals(
                1L
                ,responseBody.content.get(0).productId);
        assertEquals(
                "릴렉스 핏 크루 넥 반팔 티셔츠"
                ,responseBody.content.get(0).name);
        assertEquals(
                "image.png"
                ,responseBody.content.get(0).thumbnail);
        assertEquals(
                "무신사 스탠다드"
                ,responseBody.content.get(0).brandName);
        assertEquals(
                "상의"
                ,responseBody.content.get(0).category);
        assertEquals(
                10690
                ,responseBody.content.get(0).price);
        assertEquals(
                100
                ,responseBody.content.get(0).amount);
        assertEquals(
                69058
                ,responseBody.content.get(0).reviewNum);
        assertEquals(
                4.8
                ,responseBody.content.get(0).reviewAvg, TOLERANCE);
    }
}

참고자료

profile
어떠한 가치를 창출할 수 있을까를 고민하는 개발자. 주로 Spring으로 개발해요. https://comclothing.tistory.com/ 👈🏻티스토리 블로그로 이전 완료

0개의 댓글