TDD and Junit 5 part 3/3
เรามี model, service, repository หนนี้เราจะเพิ่ม controller
จุดหมาย
เราต้องการ REST APIs เป็น operation ของโปรเจกต์ ได้แก่
- สร้างผู้เรียน
- ขอข้อมูลผู้เรียน
- แก้ไขข้อมูลผู้เรียน
- ลบผู้เรียน
- เพิ่มคะแนนรายวิชาแก่ผู้เรียน
- ลบคะแนนรายวิชาของผู้เรียน
คิดถึงเทสก่อนเสมอ! ให้เทส drive โปรแกรมออกมา
Controller Tests
สร้าง package ชื่อ controller ไว้ใต้ src/test/java/com/pros/studentsubjects
สร้างคลาสเทสชื่อ StudentControllerTest
StudentControllerTest
คลาสเทสนี้จะทดสอบการ create, read, update และ delete ผู้เรียน โดยใช้ความสามารถของ org.springframework.test.web.servlet.MockMvc
จับคู่กับ HTTP methods ต่อไปนี้
- create — post
- read — get
- update — put
- delete — post
StudentControllerTest — สร้างผู้เรียน
// post: /students
{
"firstName": "Phai",
"lastName": "Panda",
"email": "panda_phai@mail.com"
}
package com.pros.studentsubjects.controller;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public class StudentControllerTest {
@Autowired
MockMvc mockMvc;
@Test
void createStudent() throws Exception {
mockMvc.perform(post("/students")
.contentType(MediaType.APPLICATION_JSON)
.param("firstName", "Phai")
.param("lastName", "Panda")
.param("email", "panda_phai@mail.com"))
.andExpect(status().isOk());
}
}
พอรันจะพบกับ mockMvc เป็น null ดังนั้นบอกกับ framework ให้นำเขาเข้ามา
@ExtendWith(MockitoExtension.class)
พอรันจะพบกับ mockMvc เป็น null อีก เพราะไม่รู้จัก controller ที่จะเทส เขียนเพิ่มเข้าไปอีกว่าเป็น StudentController
@WebMvcTest(StudentController.class)
@ExtendWith(MockitoExtension.class)
@WebMvcTest(StudentController.class)
public class StudentControllerTest {
...
}
พอรันจะพัง แจ้งว่าหา StudentController ไม่พบ ก็ให้เราเริ่มสร้างได้
รันให้เทสนี้พังก่อน
java.lang.AssertionError: Status expected:<200> but was:<404>
Expected :200
Actual :404
ในโค้ดเทสเราต้องการ status().isOk() ก็คือ 200 แต่เรายังไม่มีบริการ post: /students
ดังนั้นสร้างอีก
แก้ไขคลาส StudentController
package com.pros.studentsubjects.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StudentController {
@PostMapping("/students")
ResponseEntity<?> createStudent() {
return ResponseEntity.ok().build();
}
}
รันเทสอีกครั้ง — เทสผ่าน!
สังเกตนะว่าเราส่ง params ไปให้ แต่ StudentController ไม่ได้รับไปใช้เลย ในขณะนี้จะรับไปใช้หรือไม่ก็ได้ แต่ถ้าจะรับไปใช้ก็ให้เพิ่ม @RequestParam
ให้กับมัน
package com.pros.studentsubjects.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StudentController {
@PostMapping("/students")
ResponseEntity<?> createStudent(
@RequestParam String firstName,
@RequestParam String lastName,
@RequestParam String email
) {
return ResponseEntity.ok().build();
}
}
รันเทสอีกครั้ง — เทสผ่าน!
เฮ้ แต่เราต้องการ json body ไม่ใช่เหรอ ? ที่ทำไปนั่นมัน params นะ คนละอย่างกัน
อ่อ งั้นแก้ไข
@Test
void createStudent() throws Exception {
mockMvc.perform(post("/students")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"firstName\":\"Phai\",\"lastName\":\"Panda\",\"email\":\"panda_phai@mail.com\"}"))
.andExpect(status().isOk());
}
รันเทสอีกครั้ง — พัง
java.lang.AssertionError: Status expected:<200> but was:<400>
Expected :200
Actual :400
กล่าวคือปลายทางไม่มี body ไปรับ
ฉะนั้นเพิ่ม @RequestBody
โดยการสร้าง Data Transfer Objects (DTO) เป็นตัวรับผิดชอบ input นี้ ให้ชื่อว่า StudentReq หรือ StudentDto ก็ได้ อื่ม… เอาเป็น StudentReqDto แล้วกัน (ผสมซะงั้น)
StudentReqDto ให้อยู่ใน package ชื่อ dto นะ
package com.pros.studentsubjects.dto;
import lombok.Value;
@Value
public class StudentReqDto {
String firstName;
String lastName;
String email;
}
ใช้ @Value เพราะต้องการความเป็น immutable คือแก้ไขค่าไม่ได้
แก้ไขคลาส StudentController
package com.pros.studentsubjects.controller;
import com.pros.studentsubjects.dto.StudentReqDto;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StudentController {
@PostMapping("/students")
ResponseEntity<?> createStudent(@RequestBody StudentReqDto reqDto) {
return ResponseEntity.ok().build();
}
}
รันเทสอีกครั้ง — เทสผ่าน!
เฮ้ ขอเปลี่ยน 200 OK เป็น 201 Created ได้ไหม
refactor เหรอ จัดไป เพราะเทสผ่านแล้ว
แก้ไขคลาส StudentController ให้ส่ง 201 Created กลับไป
package com.pros.studentsubjects.controller;
import com.pros.studentsubjects.dto.StudentReqDto;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StudentController {
@PostMapping("/students")
ResponseEntity<?> createStudent(@RequestBody StudentReqDto reqDto) {
return new ResponseEntity<>(HttpStatus.CREATED);
}
}
รันเทสอีกครั้ง — พัง
java.lang.AssertionError: Status expected:<200> but was:<201>
Expected :200
Actual :201
แก้ไขคลาสเทส ถามหา isCreated แทน isOk
@Test
void createStudent() throws Exception {
mockMvc.perform(post("/students")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"firstName\":\"Phai\",\"lastName\":\"Panda\",\"email\":\"panda_phai@mail.com\"}"))
.andExpect(status().isCreated());
}
StudentControllerTest — ขอข้อมูลผู้เรียน
// get: /students/1
{
"firstName": "Phai",
"lastName": "Panda",
"email": "panda_phai@mail.com"
}
คิดถึงเทสก่อนเสมอ! ให้เทส drive โปรแกรมออกมา
ที่คลาส StudentControllerTest เพิ่ม
@Test
void getStudentById() throws Exception {
mockMvc.perform(post("/students")
.contentType(MediaType.APPLICATION_JSON)
.param("id", "1"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON));
}
โดย content หามาจาก
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
ไว้มาเขียนเพิ่มนะ มีประชุมครับ