SpringBoot API运行、测试与HTTP请求行为不一致问题排查
SpringBoot API 异常问题排查
问题描述
- 自定义JUnit测试类
EmployeeControllerTest发起HTTP请求时,抛出异常:jakarta.servlet.ServletException: Request processing failed: java.util.NoSuchElementException: No value present - Postman测试时,
/employees接口正常返回员工列表,但/employee(带或不带id参数)接口无任何返回 - 代码内部(主类
CommandLineRunner中)调用相关方法可正常获取预期结果
相关代码
模型类
package com.openclassrooms.api.models; import jakarta.persistence.*; import lombok.Data; @Data @Entity @Table(name = "employees") public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; private String mail; private String password; }
仓库类
package com.openclassrooms.api.repository; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; import com.openclassrooms.api.models.Employee; @Repository public interface EmployeeRepository extends CrudRepository<Employee, Long> { }
服务类
package com.openclassrooms.api.service; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.openclassrooms.api.models.Employee; import com.openclassrooms.api.repository.EmployeeRepository; @Service public class EmployeeService { @Autowired private EmployeeRepository employeeRepository; public Optional<Employee> getEmployee(final Long id) { System.out.println("getEmployee ok"); return employeeRepository.findById(id); } public Iterable<Employee> getEmployees() { System.out.println("getEmployees ok"); return employeeRepository.findAll(); } public void deleteEmployee(final Long id) { employeeRepository.deleteById(id); } public Employee saveEmployee(Employee employee) { Employee savedEmployee = employeeRepository.save(employee); return savedEmployee; } }
原控制器类(存在逻辑错误)
package com.openclassrooms.api.controller; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import com.openclassrooms.api.models.Employee; import com.openclassrooms.api.service.EmployeeService; @RestController public class EmployeeController { @Autowired private EmployeeService employeeService; @GetMapping("/employees") public Iterable<Employee> getEmployees() { Iterable<Employee> list = employeeService.getEmployees(); System.out.println(list); return list; } @GetMapping("/employee/{id}") public Employee getEmployee(@PathVariable("id") final Long id) { Optional<Employee> emp = employeeService.getEmployee(id); if (emp.isEmpty()) { Employee employe = emp.get(); System.out.println(employe.getFirstName()); return employe; } else { System.out.println("ABSENT"); return null; } } @GetMapping("/employee") public Employee getEmployee() { Optional<Employee> emp = employeeService.getEmployee(1L); if (emp.isEmpty()) { Employee employe = emp.get(); System.out.println(employe.getFirstName()); return employe; } else { System.out.println("ABSENT"); return null; } } }
主类
package com.openclassrooms.api; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import com.openclassrooms.api.models.Employee; import com.openclassrooms.api.service.EmployeeService; @SpringBootApplication public class ApiApplication implements CommandLineRunner { @Autowired private EmployeeService employeeService; public static void main(String[] args) { SpringApplication.run(ApiApplication.class, args); } public void run(String... args) throws Exception { if (employeeService.getEmployee(1L).isPresent()) { Employee emp1 = employeeService.getEmployee(1L).get(); System.out.println(emp1.getFirstName() + " " + emp1.getLastName()); } else { System.out.println("Erreur, employé absent."); } System.out.println(employeeService.getEmployees()); } }
原测试类(未模拟Service返回值)
package com.openclassrooms.api; import static org.hamcrest.CoreMatchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import com.openclassrooms.api.controller.EmployeeController; import com.openclassrooms.api.models.Employee; import com.openclassrooms.api.service.EmployeeService; @WebMvcTest(controllers = EmployeeController.class) public class EmployeeControllerTest { @Autowired private MockMvc mockMvc; @MockBean private EmployeeService employeeService; @Test public void testGetEmployees() throws Exception { Employee response = new Employee(); mockMvc.perform(get("/employee")) .andExpect(status().isOk()) .andDo(print(System.out)) .andExpect(jsonPath("$.firstName").value("Laurent")); } }
数据库初始化SQL脚本
DROP TABLE IF EXISTS employees; CREATE TABLE employees ( id INT AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(250) NOT NULL, last_name VARCHAR(250) NOT NULL, mail VARCHAR(250) NOT NULL, password VARCHAR(250) NOT NULL ) INSERT INTO employees (first_name, last_name, mail, password) VALUES ('Laurent', 'GINA', 'laurentgina@mail.com', 'laurent'), ('Sophie', 'FONCEK', 'sophiefoncek@mail.com', 'sophie'), ('Agathe', 'FEELING', 'agathefeeling@mail.com', 'agathe');
问题根源与修复方案
1. 控制器逻辑完全写反(核心问题)
原控制器中getEmployee方法的条件判断颠倒:当emp.isEmpty()为true(无数据)时调用emp.get(),直接触发NoSuchElementException;有数据时反而返回null,导致接口无内容返回。
修正后的控制器代码
package com.openclassrooms.api.controller; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; import com.openclassrooms.api.models.Employee; import com.openclassrooms.api.service.EmployeeService; @RestController public class EmployeeController { @Autowired private EmployeeService employeeService; @GetMapping("/employees") public Iterable<Employee> getEmployees() { Iterable<Employee> list = employeeService.getEmployees(); System.out.println(list); return list; } @GetMapping("/employee/{id}") public Employee getEmployee(@PathVariable("id") final Long id) { Optional<Employee> emp = employeeService.getEmployee(id); // 修正判断逻辑:存在数据则返回,不存在抛出404异常 if (emp.isPresent()) { Employee employe = emp.get(); System.out.println(employe.getFirstName()); return employe; } else { System.out.println("ABSENT"); throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Employee not found with id: " + id); } } @GetMapping("/employee") public Employee getEmployee() { Optional<Employee> emp = employeeService.getEmployee(1L); if (emp.isPresent()) { Employee employe = emp.get(); System.out.println(employe.getFirstName()); return employe; } else { System.out.println("ABSENT"); throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Employee not found with id: 1"); } } }
2. JUnit测试未模拟Service返回值
@WebMvcTest隔离控制器层,@MockBean的EmployeeService默认返回空Optional,触发控制器异常。需用Mockito模拟Service的返回数据。
修正后的测试类代码
package com.openclassrooms.api; import static org.hamcrest.CoreMatchers.is; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import com.openclassrooms.api.controller.EmployeeController; import com.openclassrooms.api.models.Employee; import com.openclassrooms.api.service.EmployeeService; import java.util.Optional; @WebMvcTest(controllers = EmployeeController.class) public class EmployeeControllerTest { @Autowired private MockMvc mockMvc; @MockBean private EmployeeService employeeService; @Test public void testGetEmployee() throws Exception { // 模拟Service返回id=1的员工数据 Employee mockEmployee = new Employee(); mockEmployee.setId(1L); mockEmployee.setFirstName("Laurent"); mockEmployee.setLastName("GINA"); mockEmployee.setMail("laurentgina@mail.com"); mockEmployee.setPassword("laurent"); when(employeeService.getEmployee(1L)).thenReturn(Optional.of(mockEmployee)); mockMvc.perform(get("/employee")) .andExpect(status().isOk()) .andDo(print()) .andExpect(jsonPath("$.firstName").value("Laurent")); } }
内容的提问来源于stack exchange,提问作者Bhorah
相关产品推荐
相关产品推荐

