본문 바로가기
Spring/JUnit

Spring에서 JUnit 테스트 진행 - REST API CRUD 테스트

by step 1 2021. 6. 27.
반응형

MockMvc를 이용하여 Controller Test를 진행한다.

MockMvc: 테스트에 필요한 기능만 가지는 가짜 객체를 만들어서 애플리케이션 서버에 배포하지 않고

스프링 MVC 동작을 재현 할 수 있는 클래스

 

perform(): 원하는 요청을 전송하는 역할

 

andDo(print()): 요청/응답 전체 메세지를 확인할 수 있다.

 

@MockBean: 해당 클래스를 Mock 처리하고 스프링에서 bean으로 등록하여 사용 위해서 선언

 

@SpringBootTest: 모든 bean이 등록되어 사용할 때 선언, 전체 테스트를 진행할 때 사용

 

https://shinsunyoung.tistory.com/52

 

SpringBoot의 MockMvc를 사용하여 GET, POST 응답 테스트하기

안녕하세요! 이번 포스팅에서는 SpringBoot 프레임워크에서 제공해주는 MockMvc를 만들고 테스트하는 방법을 알아보도록 하겠습니다. 전체 코드는 Github에서 확인이 가능합니다. ✍️ 📚 개념 정리

shinsunyoung.tistory.com

 

테스트를 진행하기 위한 코드 작성

저번 포스트에서 작성한 calculator 프로젝트를 이용한다.

 

ICalculator 인터페이스 작성

package com.example.springcalculator.component;

public interface ICalculator {

    int sum(int x, int y);
    int minus(int x, int y);
    void init();
}

 

Calculator 클래스 작성

@RequiredArgsConstructor:  final로 선언된 변수의 생성자를 만들어 준다. 따라서 기존 생성자를 지워준다.

package com.example.springcalculator.component;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
// final로 선언된 변수의 생성자를 만들어 준다.
@RequiredArgsConstructor
public class Calculator {

    private final ICalculator iCalculator;

//    public Calculator(ICalculator iCalculator){
//        this.iCalculator = iCalculator;
//    }

    public int sum(int x, int y){
        // 서버로부터 환율을 받아온다.
        iCalculator.init();
        return this.iCalculator.sum(x,y);
    }

    public  int minus(int x, int y){
        // 서버로부터 환율을 받아온다.
        iCalculator.init();
        return iCalculator.minus(x, y);
    }
}

 

DollarCalculator 클래스 작성

@RequiredArgsConstructor:  final로 선언된 변수의 생성자를 만들어 준다. 따라서 기존 생성자를 지워준다.

package com.example.springcalculator.component;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
// final로 선언된 변수의 생성자를 만들어 준다.
@RequiredArgsConstructor
public class DollarCalculator implements ICalculator {

    private int price = 1;
    private final MarketApi marketApi;

//  현재 환율 가져오기 (생성자를 이용하여 클래스 호출시 자동)
//    public DollarCalculator(MarketApi marketApi){
//        this.marketApi = marketApi;
//    }
//  현재 환율을 금액에 대입 시킨다.
    @Override
    public void init(){
        this.price = marketApi.connect();
    }


    @Override
    public int sum(int x, int y) {
        x *= price;
        y *= price;

        return x + y;
    }

    @Override
    public int minus(int x, int y) {
        x *= price;
        y *= price;

        return x - y;
    }
}

 

MarketApi 클래스 작성 (환율 값 가져오는 클래스)

package com.example.springcalculator.component;

import org.springframework.stereotype.Component;

@Component
public class MarketApi {

    public int connect(){
        return 1100;
    }
}

 

POST 방식의 controller 테스트를 진행하기 위한 DTO 생성

Req 클래스 생성 (파라미터를 받을 DTO)

package com.example.springcalculator.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Req {

    private int x;
    private int y;
}

 

Res 클래스 작성 (연산 결과와 상태값을 저장할 DTO)

package com.example.springcalculator.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Res {

    private int result;

    private Body response;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Body{
        private String resultCode = "OK";
    }
}

 

controller 작성 (POST, GET 처리)

package com.example.springcalculator.controller;

import com.example.springcalculator.component.Calculator;
import com.example.springcalculator.dto.Req;
import com.example.springcalculator.dto.Res;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class CalculatorApiController {

    private final Calculator calculator;

    @GetMapping("/sum")
    public int sum(@RequestParam int x, @RequestParam int y){
        return calculator.sum(x, y);
    }

    @PostMapping("/minus")
    public Res minus(@RequestBody Req req){
        int result = calculator.minus(req.getX(), req.getY());

        Res res = new Res();
        res.setResult(result);
        // resultCode":"OK" 데이터 추가
        res.setResponse(new Res.Body());

        return res;
    }
}

 

JUnit Test 진행할 code 작성

기본적인 Test Code

package com.example.springcalculator;

import com.example.springcalculator.component.Calculator;
import com.example.springcalculator.component.DollarCalculator;
import com.example.springcalculator.component.MarketApi;
import com.example.springcalculator.dto.Req;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
public class DallarCalculatorTest {

    // 스프링에서는 Bean 처리되기 때문에
    @MockBean
    private MarketApi marketApi;

    @Autowired
    private Calculator calculator;

    @Test
    public void dollarCalculatorTest(){
        Mockito.when(marketApi.connect()).thenReturn(3000);

        int sum = calculator.sum(10, 10);
        int minus = calculator.minus(10, 10);

        Assertions.assertEquals(60000, sum);
        Assertions.assertEquals(0, minus);
    }

}

 

Controller를 이용하여 MVC 테스트를 진행할 코드 작성

package com.example.springcalculator.controller;

import com.example.springcalculator.component.Calculator;
import com.example.springcalculator.component.DollarCalculator;
import com.example.springcalculator.component.MarketApi;
import com.example.springcalculator.dto.Req;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

// web을 test하기 때문에 선언
// SpringBootTest을 이용하여 진행해도 되지만 SpringBootTest는 bean에 등록된 모든 자원을 이용하고
// WebMvcTest는 해당 클래스만 이용하기 때문에 자원을 아낄 수 있다.
@WebMvcTest(CalculatorApiController.class)
// MVC와 관련된 Bean을 올린다
@AutoConfigureWebMvc
// 테스트를 진행할 controller에서 사용하기 때문에 해당 클래스를 주입해준다.
@Import({Calculator.class, DollarCalculator.class})
public class CalculatorApiControllerTest {

    @MockBean
    private MarketApi marketApi;

    @Autowired
    private MockMvc mockMvc;

    @BeforeEach
    public void init(){
        Mockito.when(marketApi.connect()).thenReturn(3000);
    }

    // get 방식 테스트
    @Test
    public void sumTest() throws Exception {
        // http://localhost:8080/api/sum
        // 해당 URL로 접속
        mockMvc.perform(
                MockMvcRequestBuilders.get("http://localhost:8080/api/sum")
                        // 파라미터 값 추가
                .queryParam("x", "10")
                .queryParam("y", "10")
                // 기대하는 값 입력
        ).andExpect(
                // 200 ok
                MockMvcResultMatchers.status().isOk()
        ).andExpect(
                // 합: 60000
                MockMvcResultMatchers.content().string("60000")
                // 실행
        ).andDo(MockMvcResultHandlers.print());
    }

    // POST 방식 테스트
    @Test
    public void minusTest() throws Exception {

        Req req = new Req();
        req.setX(10);
        req.setY(10);
        // req를 json 형태로 변환
        String json = new ObjectMapper().writeValueAsString(req);
        System.out.println(json);
        
        // 해당 URL로 접속
        mockMvc.perform(
                MockMvcRequestBuilders.post("http://localhost:8080/api/minus")
                .contentType(MediaType.APPLICATION_JSON)
                .content(json)
          // 기대하는 값 입력      
        ).andExpect(
                MockMvcResultMatchers.status().isOk()
            
        ).andExpect(
                // json의 result 항목이 0으로 나오는지 확인
                MockMvcResultMatchers.jsonPath("$.result").value("0")
        ).andExpect(
                // json의 response.resultCode 항목이 OK으로 나오는지 확인
                MockMvcResultMatchers.jsonPath("$.response.resultCode").value("OK")
                // 실행           
        ).andDo(MockMvcResultHandlers.print());
    }
}

 

실행 화면

get 방식 test

post 방식 test

 

 

 

반응형

'Spring > JUnit' 카테고리의 다른 글

jacoco를 이용하여 테스트 커버리지 확인하는 방법  (0) 2021.06.27
JUnit  (0) 2021.06.27