TDD and Junit 5 part 3/3

Phai Panda
3 min readMar 23, 2023

--

เรามี 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 ไม่พบ ก็ให้เราเริ่มสร้างได้

สร้างคลาส StudentController
วางไว้ที่ controller package

รันให้เทสนี้พังก่อน

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;

ไว้มาเขียนเพิ่มนะ มีประชุมครับ

--

--