본문 바로가기
Back-end/테스트

Spring Boot MVC Controller Test (컨트롤러 테스트)

by javapp 자바앱 2022. 11. 5.
728x90

목적

  • Spring MVC Web Controllers 테스트
  • HTTP requests 생성과 전송
  • HTTP response 검증 (status code, view name, model attributes)

 

 

Process

  • @AutoConfigureMockMvc
  • MockMvc 주입
  • web requests 수행
  • Expectations 정의
  • Assert results

 

 


 

테스트 클래스

package com.luv2code.springmvc;

@TestPropertySource("/application.properties")
@AutoConfigureMockMvc
@SpringBootTest
public class GreadeBookControllerTest
{
    @Autowired
    private JdbcTemplate jdbc;

    @Autowired
    private MockMvc mockMvc;

    @Mock
    private StudentAndGradeService studentAndGradeServiceMock;

    @Autowired
    private StudentDao studentDao;

    private static MockHttpServletRequest request;

}

@TestPropertySource("/application.properties") // 설정파일을 설정
@AutoConfigureMockMvc  // 컨트롤러뿐만 아니라 @Service, @Repository 객체들도 모두 메모리에 올린다.

 

 

JdbcTemplate

    @Autowired
    private JdbcTemplate jdbc;

직접적으로 sql 실행 가능

 

 

MockMvc

    @Autowired
    private MockMvc mockMvc;

서블릿 컨테이너 사용하지 않고, 스프링 MVC 동작을 재현할 수 있는 클래스

 

 

 

@BeforeAll

    private static MockHttpServletRequest request;

    @BeforeAll
    public static void setup(){ // static 전역으로 정의
        request= new MockHttpServletRequest();
        request.setParameter("firstname","java");
        request.setParameter("lastname","app");
        request.setParameter("emailAddress","javapp@tistory.com");
    }

테스트 전 uri 파라미터 설정

 

 

 

@BeforeEach

    @BeforeEach
    public void beforeEach(){
        // 학생 생성
        jdbc.execute("INSERT INTO student(id, firstname,lastname, email_address)"+
                "values(1,'java','app','javapp@tistory)')");
    }

테스트 데이터 삽입

 

@AfterEach

    @AfterEach
    public void setupAfterTransaction(){
        jdbc.execute("DELETE FROM student");
    }

테스트 데이터 제거

 


 

GET 통신 테스트

    @DisplayName("'/' 에 GET 통신으로 어떤 view 네임이 반환되는지")
    @Test
    public void getStudentHttpRequest() throws Exception{
        //given
        CollegeStudent studentOne = new GradebookCollegeStudent("java", "app","javapp@tistory.com");
        CollegeStudent studentTwo= new GradebookCollegeStudent("java2", "app2","javapp2@tistory.com");

        //when
        List<CollegeStudent> collegeStudentList = new ArrayList<>(Arrays.asList(studentOne,studentTwo));
        // getGradebook의 반환값을 설정
        when(studentAndGradeServiceMock.getGradebook()).thenReturn(collegeStudentList);
        //then
        assertIterableEquals(collegeStudentList, studentAndGradeServiceMock.getGradebook());


        // controller 테스트 결과와 view name 테스트
        MvcResult mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/")) // path로 접속
                .andExpect(status().isOk()).andReturn();

        ModelAndView mav= mvcResult.getModelAndView();

        // Assert 테스트
        ModelAndViewAssert.assertViewName(mav, "index");
    }

URI 접속후 view name이 index 인지 테스트

 

       // controller 테스트 결과와 view name 테스트
        MvcResult mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/")) // path로 접속
                .andExpect(status().isOk()).andReturn();

        ModelAndView mav= mvcResult.getModelAndView();

        // Assert 테스트
        ModelAndViewAssert.assertViewName(mav, "index");

 

 

Controller

	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String getStudents(Model m) {
		Iterable<CollegeStudent> collegeStudents = studentAndGradeService.getGradebook();
		m.addAttribute("students", collegeStudents);
		return "index";
	}

 

 


 

 

POST 연결 테스트

    @DisplayName("'/' 에 POST 연결, 데이터 전송")
    @Test
    public void createStudentHttpRequest() throws Exception{

        // 대학생 객체 생성
        CollegeStudent studentOne = new CollegeStudent("java","app","javapp@tistory.com");

        List<CollegeStudent> collegeStudentList = new ArrayList<>(Arrays.asList(studentOne));


        // getGradebook() 했을 떄 collegeStudentList가 반환될 때
        when(studentAndGradeServiceMock.getGradebook()).thenReturn(collegeStudentList);

        // collegeStudentList 반복값과 getGradebook 동작으로 반횐된 값이 같은지
        assertIterableEquals(collegeStudentList, studentAndGradeServiceMock.getGradebook());

        MvcResult mvcResult= mockMvc.perform(post("/")
                .contentType(MediaType.APPLICATION_JSON)
                .param("firstname",request.getParameterValues("firstname"))
                .param("lastname",request.getParameterValues("lastname"))
                .param("emailAddress",request.getParameterValues("emailAddress"))
        ).andExpect(status().isOk()).andReturn();

        ModelAndView mav = mvcResult.getModelAndView();

        ModelAndViewAssert.assertViewName(mav,"index");


        // controller 요청 했을 때
        // database 값 체크
        CollegeStudent verifyStudent = studentDao.findByEmailAddress("javapp@tistory.com");
        assertNotNull(verifyStudent,"학생 찾음");
    }

 

 

Controller

	@PostMapping(value="/")
	public String createStudent(@ModelAttribute("student") CollegeStudent student, Model model)
	{
		studentAndGradeService.createStudent(student.getFirstname(), student.getLastname(),student.getEmailAddress());
		Iterable<CollegeStudent> collegeStudents = studentAndGradeService.getGradebook();
		model.addAttribute("students",collegeStudents);
		return "index";
	}

 

 


 

 

    @DisplayName("학생 row 값 지우기")
    @Test
    public void deleteStudentHttpRequest() throws Exception{
        //1. @BeforeEach 에서 1번 id 에 학생을 삽입한 값 존재 확인
        assertTrue(studentDao.findById(1).isPresent());

        //2. delete uri 테스트
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
                .get("/delete/student/{id}", 1))
                .andExpect(status().isOk()).andReturn();
        ModelAndView mav= mvcResult.getModelAndView();
        ModelAndViewAssert.assertViewName(mav, "index");

        assertFalse(studentDao.findById(1).isPresent()); //삭제됐음 성공
    }

    @DisplayName("존재하지않은 학생 삭제할때 에러 페이지")
    @Test
    public void deleteStudentHttpRequestErrorPage() throws Exception{
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
                .get("/delete/student/{id}", 0))
                .andExpect(status().isOk()).andReturn();
        ModelAndView mav = mvcResult.getModelAndView();
        ModelAndViewAssert.assertViewName(mav,"error");
    }

 

 

전체코드

@TestPropertySource("/application-test.properties")
@AutoConfigureMockMvc
@SpringBootTest
public class GreadeBookControllerTest
{
    @Autowired
    private JdbcTemplate jdbc;

    @Autowired
    private MockMvc mockMvc;

    @Mock
    private StudentAndGradeService studentAndGradeServiceMock;

    @Autowired
    private StudentDao studentDao;

    @Autowired
    private MathGradesDao mathGradesDao;

    @Autowired
    private StudentAndGradeService studentAndGradeService;

    private static MockHttpServletRequest request;

    @Value("${spl.script.create.student}")
    private String sqlAddstudent;
    @Value("${sql.script.create.math.grade}")
    private String sqlAddMathGrade;
    @Value("${spl.script.create.science.grade}")
    private String sqlAddScienceGrade;
    @Value("${spl.script.create.history.grade}")
    private String sqlAddHistoryGrade;
    @Value("${sql.script.delete.stduent}")
    private String sqlDeleteStudent;
    @Value("${sql.script.delete.math.grade}")
    private String sqlDeleteMathGrade;
    @Value("${sql.script.delete.science.grade}")
    private String sqlDeleteScienceGrade;
    @Value("${sql.script.delete.history.grade}")
    private String sqlDeleteHistoryGrade;

    @BeforeAll
    public static void setup(){ // static 전역으로 정의
        request= new MockHttpServletRequest();
        request.setParameter("firstname","java2");
        request.setParameter("lastname","app2");
        request.setParameter("emailAddress","javapp2@tistory.com");
    }


    @BeforeEach
    public void beforeEach(){
        // 학생 생성
        jdbc.execute(sqlAddstudent);
        jdbc.execute(sqlAddMathGrade);
        jdbc.execute(sqlAddScienceGrade);
        jdbc.execute(sqlAddHistoryGrade);
    }


    @DisplayName("'/' 에 GET 통신으로 어떤 view 네임이 반환되는지")
    @Test
    public void getStudentHttpRequest() throws Exception{
        //given
        CollegeStudent studentOne = new GradebookCollegeStudent("java", "app","javapp@tistory.com");
        CollegeStudent studentTwo= new GradebookCollegeStudent("java2", "app2","javapp2@tistory.com");

        //when
        List<CollegeStudent> collegeStudentList = new ArrayList<>(Arrays.asList(studentOne,studentTwo));
        // getGradebook의 반환값을 설정
        when(studentAndGradeServiceMock.getGradebook()).thenReturn(collegeStudentList);
        //then
        assertIterableEquals(collegeStudentList, studentAndGradeServiceMock.getGradebook());


        // controller 테스트 결과와 view name 테스트
        MvcResult mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/")) // path로 접속
                .andExpect(status().isOk()).andReturn();

        ModelAndView mav= mvcResult.getModelAndView();

        // Assert 테스트
        ModelAndViewAssert.assertViewName(mav, "index");
    }

    @DisplayName("'/' 에 POST 연결, 데이터 전송")
    @Test
    public void createStudentHttpRequest() throws Exception{

        // 대학생 객체 생성
        CollegeStudent studentOne = new CollegeStudent("java2","app2","javapp2@tistory.com");

        List<CollegeStudent> collegeStudentList = new ArrayList<>(Arrays.asList(studentOne));


        // getGradebook() 했을 떄 collegeStudentList가 반환될 때
        when(studentAndGradeServiceMock.getGradebook()).thenReturn(collegeStudentList);

        // collegeStudentList 반복값과 getGradebook 동작으로 반횐된 값이 같은지
        assertIterableEquals(collegeStudentList, studentAndGradeServiceMock.getGradebook());

        MvcResult mvcResult= mockMvc.perform(post("/")
                .contentType(MediaType.APPLICATION_JSON)
                .param("firstname",request.getParameterValues("firstname"))
                .param("lastname",request.getParameterValues("lastname"))
                .param("emailAddress",request.getParameterValues("emailAddress"))
        ).andExpect(status().isOk()).andReturn();

        ModelAndView mav = mvcResult.getModelAndView();

        ModelAndViewAssert.assertViewName(mav,"index");


        // controller 요청 했을 때
        // database 값 체크
        CollegeStudent verifyStudent = studentDao.findByEmailAddress("javapp@tistory.com");
        assertNotNull(verifyStudent,"학생 찾음");
    }

    @DisplayName("학생 row 값 지우기")
    @Test
    public void deleteStudentHttpRequest() throws Exception{
        //1. @BeforeEach 에서 1번 id 에 학생을 삽입한 값 존재 확인
        assertTrue(studentDao.findById(1).isPresent());

        //2. delete uri 테스트
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
                .get("/delete/student/{id}", 1))
                .andExpect(status().isOk()).andReturn();
        ModelAndView mav= mvcResult.getModelAndView();
        ModelAndViewAssert.assertViewName(mav, "index");

        assertFalse(studentDao.findById(1).isPresent()); //삭제됐음 성공
    }

    @DisplayName("존재하지않은 학생 삭제할때 에러 페이지")
    @Test
    public void deleteStudentHttpRequestErrorPage() throws Exception{
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders
                .get("/delete/student/{id}", 0))
                .andExpect(status().isOk()).andReturn();
        ModelAndView mav = mvcResult.getModelAndView();
        ModelAndViewAssert.assertViewName(mav,"error");
    }

    /* 새로운 내용 시작 */
    @Test
    public void 존재하않는학생정보요청_HttpRequest() throws Exception{
        assertFalse(studentDao.findById(0).isPresent());

        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/studentInformation/{id}", 0))
                .andExpect(status().isOk()).andReturn();

        ModelAndView mav = mvcResult.getModelAndView();

        ModelAndViewAssert.assertViewName(mav,"error");
    }

    @Test
    public void 유효한학생정보요청() throws  Exception{
        assertTrue(studentDao.findById(1).isPresent());

        GradebookCollegeStudent student = studentAndGradeService.studentInformation(1);

        assertEquals( 1, student.getStudentGrades().getMathGradeResults().size());

        MvcResult mvcResult = this.mockMvc.perform(post("/grades")
                .contentType(MediaType.APPLICATION_JSON)
                .param("grade", "85.00")
                .param("gradeType","math")
                .param("studentId", "1")
        ).andExpect(status().isOk()).andReturn();

        ModelAndView mav= mvcResult.getModelAndView();

        ModelAndViewAssert.assertViewName(mav, "studentInformation");


        // 학생이 잘 생성되었는지 등록확인
        student = studentAndGradeService.studentInformation(1);

        assertEquals(2, student.getStudentGrades().getMathGradeResults().size());
    }

    @Test
    public void 학생정보가_없을떄_성적생성() throws Exception
    {
        MvcResult mvcResult = this.mockMvc.perform(post("/grades")
                .contentType(MediaType.APPLICATION_JSON)
                .param("grade", "85.00")
                .param("gradeType","math")
                .param("studentId", "0") // 0 : 존재하지 않는 학생 아이디
        ).andExpect(status().isOk()).andReturn();

        ModelAndView mav= mvcResult.getModelAndView();

        ModelAndViewAssert.assertViewName(mav, "error");
    }

    @Test
    public void 등급유형이나_제목오류() throws Exception
    {
        MvcResult mvcResult = this.mockMvc.perform(post("/grades")
                .contentType(MediaType.APPLICATION_JSON)
                .param("grade", "85.00")
                .param("gradeType","literature") // 없는 과목
                .param("studentId", "1") //
        ).andExpect(status().isOk()).andReturn();

        ModelAndView mav= mvcResult.getModelAndView();

        ModelAndViewAssert.assertViewName(mav, "error");
    }

    @Test
    public void 성적삭제() throws Exception
    {
        // 실제 성적 검색 확인
        Optional<MathGrade> mathGrade = mathGradesDao.findById(1);

        assertTrue(mathGrade.isPresent());

        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/grades/{id}/{gradeType}",1,"math"))
                .andExpect(status().isOk()).andReturn();

        ModelAndView mav = mvcResult.getModelAndView();

        ModelAndViewAssert.assertViewName(mav, "studentInformation");

        mathGrade = mathGradesDao.findById(1);
        assertFalse(mathGrade.isPresent());
    }



    @AfterEach
    public void setupAfterTransaction(){
        jdbc.execute(sqlDeleteStudent);
        jdbc.execute(sqlDeleteMathGrade);
        jdbc.execute(sqlDeleteScienceGrade);
        jdbc.execute(sqlDeleteHistoryGrade);
    }
}

org.opentest4j.AssertionFailedError: 학생 찾음 ==> expected: not <null>

 

 

오타때문에 실패할 수 있다.

 

댓글