深化クラス-3週目-2


2022年4月16日(土)
[スパルタコードクラブ]Spring深化クラス-3週目-2

◇Mockito mockテストユニット使用

  • Mockitoフレームワーク:
  • Mockオブジェクトを簡単に作成する方法
  • @ExtendWith(MockitoExtension.class) // Mockito 사용함
    class ProductServiceTest {
        @Mock // 해당 repository를 mock으로 생성해서 사용
        ProductRepository productRepository;
    
        @Test
        @DisplayName("관심 상품 희망가 - 최저가 이상으로 변경")
        void updateProduct_Normal() {
            // given
            Long productId = 100L;
            int myprice = MIN_MY_PRICE + 1000;
    
            ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto(
                    myprice
            );
    
            Long userId = 777L;
            ProductRequestDto  requestProductDto = new ProductRequestDto(
                    "오리온 꼬북칩 초코츄러스맛 160g",
                    "https://shopping-phinf.pstatic.net/main_2416122/24161228524.20200915151118.jpg",
                    "https://search.shopping.naver.com/gate.nhn?id=24161228524",
                    2350
            );
    
            Product product = new Product(requestProductDto, userId);
    
            ProductService productService = new ProductService(productRepository);
            when(productRepository.findById(productId))
                    .thenReturn(Optional.of(product));
    
            // when
            Product result = productService.updateProduct(productId, requestMyPriceDto);
    
            // then
            assertEquals(myprice, result.getMyprice());
        }
    
        @Test
        @DisplayName("관심 상품 희망가 - 최저가 미만으로 변경")
        void updateProduct_Failed() {
            // given
            Long productId = 100L;
            int myprice = MIN_MY_PRICE - 50;
    
            ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto(
                    myprice
            );
    
            ProductService productService = new ProductService(productRepository);
    
            // when // exception을 만듦
            Exception exception = assertThrows(IllegalArgumentException.class, () -> {
                productService.updateProduct(productId, requestMyPriceDto);
            });
    
            // then
            assertEquals(
                    "유효하지 않은 관심 가격입니다. 최소 " + MIN_MY_PRICE + " 원 이상으로 설정해 주세요.",
                    exception.getMessage()
            ); // 예상값, 실제값
        }
    }

    ◇スプリングガイドによる集積テスト

  • 統合テスト:2つ以上のモジュールの接続状態をテストします.モジュール間の接続を検証できるエラー
  • 設計
  • "@SpringBootTest":テスト時にスプリングを作動させる方法
  • Spring IoC、DB CRUDは
  • 利用可能
  • @Order(1),@Order(2):ソート可能テスト
  • は、デバイステストよりも長い時間を使用します.
  • 例(サービスとレポートを一緒にテスト)
  • @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // 랜덤 포트로 Spring 실행
    @TestInstance(TestInstance.Lifecycle.PER_CLASS) // Class 별 테스트 실행? 구글링 참고
    @TestMethodOrder(MethodOrderer.OrderAnnotation.class) // 순차적으로 테스트 실행
    class ProductIntegrationTest {
        @Autowired // Bean 사용 가능
        ProductService productService;
    
        Long userId = 100L;
        Product createdProduct = null;
        int updatedMyPrice = -1;
    
        @Test
        @Order(1) // Order 순서대로 실행
        @DisplayName("신규 관심상품 등록")
        void test1() {
            // given
            String title = "Apple <b>에어팟</b> 2세대 유선충전 모델 (MV7N2KH/A)";
            String imageUrl = "https://shopping-phinf.pstatic.net/main_1862208/18622086330.20200831140839.jpg";
            String linkUrl = "https://search.shopping.naver.com/gate.nhn?id=18622086330";
            int lPrice = 77000;
            ProductRequestDto requestDto = new ProductRequestDto(
                    title,
                    imageUrl,
                    linkUrl,
                    lPrice
            );
    
            // when
            Product product = productService.createProduct(requestDto, userId);
    
            // then
            assertNotNull(product.getId());
            assertEquals(userId, product.getUserId());
            assertEquals(title, product.getTitle());
            assertEquals(imageUrl, product.getImage());
            assertEquals(linkUrl, product.getLink());
            assertEquals(lPrice, product.getLprice());
            assertEquals(0, product.getMyprice());
            createdProduct = product; // test2 에서 사용할 객체 만들기
        }
    
        @Test
        @Order(2)
        @DisplayName("신규 등록된 관심상품의 희망 최저가 변경")
        void test2() {
            // given
            Long productId = this.createdProduct.getId();
            int myPrice = 70000;
            ProductMypriceRequestDto requestDto = new ProductMypriceRequestDto(myPrice);
    
            // when
            Product product = productService.updateProduct(productId, requestDto);
    
            // then
            assertNotNull(product.getId());
            assertEquals(userId, product.getUserId());
            assertEquals(this.createdProduct.getTitle(), product.getTitle());
            assertEquals(this.createdProduct.getImage(), product.getImage());
            assertEquals(this.createdProduct.getLink(), product.getLink());
            assertEquals(this.createdProduct.getLprice(), product.getLprice());
            assertEquals(myPrice, product.getMyprice());
            this.updatedMyPrice = myPrice; // test3에서 사용할 객체 만들기
        }
    
        @Test
        @Order(3)
        @DisplayName("회원이 등록한 모든 관심상품 조회")
        void test3() {
            // given
            
            // when
            List<Product> productList = productService.getProducts(userId);
    
            // then
            // 1. 전체 상품에서 테스트에 의해 생성된 상품 찾아오기 (상품의 id 로 찾음)
            Long createdProductId = this.createdProduct.getId();
            Product foundProduct = productList.stream()
                    .filter(product -> product.getId().equals(createdProductId))
                    .findFirst()
                    .orElse(null);
            
            // 2. Order(1) 테스트에 의해 생성된 상품과 일치하는지 검증
            assertNotNull(foundProduct);
            assertEquals(userId, foundProduct.getUserId());
            assertEquals(this.createdProduct.getId(), foundProduct.getId());
            assertEquals(this.createdProduct.getTitle(), foundProduct.getTitle());
            assertEquals(this.createdProduct.getImage(), foundProduct.getImage());
            assertEquals(this.createdProduct.getLink(), foundProduct.getLink());
            assertEquals(this.createdProduct.getLprice(), foundProduct.getLprice());
            
            // 3. Order(2) 테스트에 의해 myPrice 가격이 정상적으로 업데이트되었는지 검증
            assertEquals(this.updatedMyPrice, foundProduct.getMyprice());
        }
    }

    ◇テストスプリングMVC(今回のテストコントローラで使用)

  • 適用事項
  • //build.gradle dependency 에 넣고 실행
    testImplementation 'org.springframework.security:spring-security-test'
    // test > mvc > MockSpringSecurityFilter.js
    
    public class MockSpringSecurityFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) {}
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            SecurityContextHolder.getContext()
                    .setAuthentication((Authentication) ((HttpServletRequest) req).getUserPrincipal());
            chain.doFilter(req, res);
        }
    
        @Override
        public void destroy() {
            SecurityContextHolder.clearContext();
        }
    }
  • @WebMvcTest(
            controllers = {UserController.class, ProductController.class},
            excludeFilters = {
                    @ComponentScan.Filter(
                            type = FilterType.ASSIGNABLE_TYPE,
                            classes = WebSecurityConfig.class
                    )
            }
    )
    class UserProductMvcTest {
        private MockMvc mvc;
    
        private Principal mockPrincipal;
    
        @Autowired
        private WebApplicationContext context;
    
        @Autowired
        private ObjectMapper objectMapper;
    
        @MockBean
        UserService userService;
    
        @MockBean
        KakaoUserService kakaoUserService;
    
        @MockBean
        ProductService productService;
    
        @BeforeEach
        public void setup() {
            mvc = MockMvcBuilders.webAppContextSetup(context)
                    .apply(springSecurity(new MockSpringSecurityFilter()))
                    .build();
        }
    
        private void mockUserSetup() {
            // Mock 테스트 유져 생성
            String username = "제이홉";
            String password = "hope!@#";
            String email = "[email protected]";
            UserRoleEnum role = UserRoleEnum.USER;
            User testUser = new User(username, password, email, role);
            UserDetailsImpl testUserDetails = new UserDetailsImpl(testUser);
            mockPrincipal = new UsernamePasswordAuthenticationToken(testUserDetails, "", testUserDetails.getAuthorities());
        }
    
        @Test
        @DisplayName("로그인 view")
        void test1() throws Exception {
            // when - then
            mvc.perform(get("/user/login"))
                    .andExpect(status().isOk())
                    .andExpect(view().name("login"))
                    .andDo(print());
        }
    
        @Test
        @DisplayName("회원 가입 요청 처리")
        void test2() throws Exception {
            // given
            MultiValueMap<String, String> signupRequestForm = new LinkedMultiValueMap<>();
            signupRequestForm.add("username", "제이홉");
            signupRequestForm.add("password", "hope!@#");
            signupRequestForm.add("email", "[email protected]");
            signupRequestForm.add("admin", "false");
    
            // when - then
            mvc.perform(post("/user/signup")
                            .params(signupRequestForm)
                    )
                    .andExpect(status().is3xxRedirection())
                    .andExpect(view().name("redirect:/user/login"))
                    .andDo(print());
        }
    
        @Test
        @DisplayName("신규 관심상품 등록")
        void test3() throws Exception {
            // given
            this.mockUserSetup();
            String title = "Apple <b>에어팟</b> 2세대 유선충전 모델 (MV7N2KH/A)";
            String imageUrl = "https://shopping-phinf.pstatic.net/main_1862208/18622086330.20200831140839.jpg";
            String linkUrl = "https://search.shopping.naver.com/gate.nhn?id=18622086330";
            int lPrice = 77000;
            ProductRequestDto requestDto = new ProductRequestDto(
                    title,
                    imageUrl,
                    linkUrl,
                    lPrice
            );
    
            String postInfo = objectMapper.writeValueAsString(requestDto);
    
            // when - then
            mvc.perform(post("/api/products")
                            .content(postInfo)
                            .contentType(MediaType.APPLICATION_JSON)
                            .accept(MediaType.APPLICATION_JSON)
                            .principal(mockPrincipal)
                    )
                    .andExpect(status().isOk())
                    .andDo(print());
        }
    }

    ◇他各种SpringBoot Annotation的哦