Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/342.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/spring/12.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java RESTAPI控制器的单元测试_Java_Spring_Rest_Spring Mvc_Mockito - Fatal编程技术网

Java RESTAPI控制器的单元测试

Java RESTAPI控制器的单元测试,java,spring,rest,spring-mvc,mockito,Java,Spring,Rest,Spring Mvc,Mockito,我正在尝试使用mockito对spring控制器进行测试,但它不起作用 这是我的控制器: @RestController public class CandidateController { private static final Logger log = LoggerFactory.getLogger(CandidateController.class); private CandidateService candidateService; @Autowired

我正在尝试使用mockito对spring控制器进行测试,但它不起作用

这是我的控制器:

@RestController
public class CandidateController {

    private static final Logger log = LoggerFactory.getLogger(CandidateController.class);
    private CandidateService candidateService;

    @Autowired
    public CandidateController(CandidateService candidateService) {
        this.candidateService = candidateService;
    }

    @GetMapping("/candidates")
    public ResponseEntity<List<Candidate>> getAllCandidates() {
        List<Candidate> candidates = candidateService.findAll();
        log.info("Candidates list size = {}", candidates.size());
        if (candidates.size() == 0) {
            return ResponseEntity.noContent().build();
        }
        return ResponseEntity.ok(candidates);
    }


    @GetMapping("/candidates/{id}")
    public ResponseEntity<Candidate> getCandidateById(@PathVariable int id) {
        Candidate candidate = candidateService.findById(id);
        if (candidate != null) {
            return ResponseEntity.ok(candidate);
        } else {
            log.info("Candidate with id = {} not found", id);
            return ResponseEntity.notFound().build();
        }

    }

    @GetMapping("/candidates/name/{name}")
    public ResponseEntity<List<Candidate>> getCandidatesWhereNameLike(@PathVariable String name) {
        List<Candidate> candidates = candidateService.findByLastNameLike("%" + name + "%");
        log.info("Candidates by name list size = {}", candidates.size());
        if (candidates.isEmpty()) {
            return ResponseEntity.noContent().build();
        }
        return ResponseEntity.ok(candidates);
    }

    @PostMapping("/candidates/create")
    public ResponseEntity<Object> postCandidate(@Valid @RequestBody Candidate candidate) {
        Candidate newCandidate = candidateService.save(candidate);
        if (newCandidate != null) {
            URI location = ServletUriComponentsBuilder
                    .fromCurrentRequest()
                    .path("/{id}")
                    .buildAndExpand(newCandidate.getId())
                    .toUri();
            return ResponseEntity.created(location).build();
        } else {
            log.info("Candidate is already existing or null");
            return ResponseEntity.unprocessableEntity().build();
        }

    }

    @PutMapping("/candidates/{id}")
    public ResponseEntity<Object> updateCandidate(@PathVariable int id, @RequestBody Candidate candidate) {
        candidateService.update(candidate, id);
        candidate.setId(id);
        return ResponseEntity.noContent().build();
    }

    @DeleteMapping("/candidates/{id}")
    public ResponseEntity<Void> deleteCandidate(@PathVariable int id) {
        candidateService.deleteById(id);
        return ResponseEntity.noContent().build();
    }


我期待着一个404未找到的响应,但我得到了一个204无内容的响应

ResponseEntity.noContent()返回204个代码,因此如果希望控制器返回404,则应将控制器类更改为返回ResponseEntity.notFound()

我将尝试给您一些指导原则,可能会对您有所帮助:

  • 从单元测试类文件中删除该静态列表和候选定义。这会造成混乱,因为测试应该彼此隔离,并且在所有测试之间共享一个候选对象。只需在测试类中创建一个静态getATestCandidate()方法,每次都会给您一个新的Candidate()。(检查Java中的静态成员与静态方法)如果您稍后看到有其他测试类需要候选对象,请将此方法移动到单独的Util类中,并从不同的测试中调用它,或者更好地为候选对象创建一个Builder类。(检查生成器设计模式)

  • 使用Spring MVC测试框架,您可以检查整个端点基础设施,包括HTTP状态代码、输入和输出序列化、响应体、重定向等。不要通过测试不相关的内容来偏离这一点:在findByIdOk()的第一部分测试您正在测试自己的模拟。

  • 不要忘记单元测试的基本AAA概念(Arrange、Act、Assert),它也适用于MVC测试。这应该是测试的安排部分,您正在设置控制器协作者(candidateService),以便在id调用时返回候选对象。第一行可以,但调用它并确保id为1是无用的,因为您指示模拟返回该候选对象,而现在您测试它是否返回了该候选对象?(您应该相信Mockito会这样做)=>从findByIdOk()中删除第2、3和4行

    findByIdOk()测试方法的另一个改进是使用模拟MVC fluent API检查您的状态和响应内容

    因此,您的按id查找方法可能会变成(检查点3以了解我重命名id的原因):

    更喜欢使用json路径单独检查json字段,而不是作为一个整体检查整个json正文

    现在考虑一下测试您的模拟协作者候选服务返回id为1的候选对象,而您已经指示它返回id为1的候选对象(这并不能证明任何事情)之间的区别测试控制器单元在查询特定候选id时,是否能够以JSON的形式返回候选资源表示,其中包含所有候选字段

  • 因为对于同一个控制器端点,您可能会有多个测试方法,请以提示性的方式命名您的测试方法,以解释您要测试的内容。通过这种方式,您可以记录您的测试,这些测试也将变得可维护。稍后,其他人将很容易了解测试应该做什么以及如何进行在整个应用程序中使用命名约定甚至是一种很好的做法
  • 例如 在特定的测试类中,而不是创建测试

    @Test
    public void findAll() {
    ...
    }
    
    创建一个更具建议性的名称,该名称还包括您正在操作的资源

    @Test
    public void shouldGetCandidatesList() {
    ...
    }
    

  • 现在进入删除端点和服务实现。您可以将对service.deleteById()的调用放入try-catch块中,捕获ResourceNotFound异常并从控制器返回404
  • 您的删除服务可以是这样的,因为您知道如果您尝试删除不存在的候选项,该服务的API应该抛出ResourceNotFoundException:

    @DeleteMapping("/candidates/{id}")
    public ResponseEntity<Void> deleteCandidate(@PathVariable int id) {
        try{
            candidateService.deleteById(id);
        } catch(ResourceNotFoundException e) {
           ResponseEntity.notFound().build()
        }
        return ResponseEntity.noContent().build();
    }
    
    请调整get端点的测试,以同时检查JSON主体。如果测试仅测试端点返回某个响应主体时的状态,则仅完成了一半


    还请检查一些有关如何构造端点的文档。您在这里所做的可能是工作和编译,但这并不意味着它是正确的。我指的是(“/candidates/name/{name}”,“/candidates/create”)。

    感谢您的回答:) 现在我将控制器更改为:

    @DeleteMapping("/candidates/{id}")
    public ResponseEntity<Void> deleteCandidate(@PathVariable int id) {
        try {
            candidateService.deleteById(id);
        } catch (ResourceNotFoundException e) {
           return ResponseEntity.notFound().build();
        }
        return ResponseEntity.noContent().build();
    
    }
    

    但是当删除候选项时应该返回404不返回任何内容,我期待的是404


    @试验 public void应在DeleteCandidatedOnExist()引发异常时返回NoContentWhen{

        Candidate candidate = getATestCandidate();
    
        doNothing().when(candidateService).deleteById(anyInt());
    
        mockMvc.perform(
                delete("/candidates/{id}", candidate.getId())
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isNoContent());
    
    }
    

    谢谢!@IoanM我也编辑了我的上一个anwser,一切都很好,除了一个小问题:)请检查我的编辑以进行删除测试。当你想从你的控制器返回404时,你应该指示candidateService抛出ResourceNotFoundException。先生,我添加了try{candidateService.deleteById(id)}catch(ResourceNotFoundException e){..到我的控制器,但我不知道如何在测试中检查它,因为它是无效的。我正在做:doNothing()。when(candidateService).deleteById(anyInt());对吗?请阅读我的文章的结尾,因为我在看到您的问题后编辑了它。when(candidateService.deleteById(anyInt())。然后抛出(new ResourceNotFoundException());这是如何设置服务以引发异常,然后检查未找到的异常..我不知道我何时尝试引发异常,但它不起作用,无论如何我是这样做的doThrow(new ResourceNotFoundException(candidate.getId()).when(candidateService).deleteById(anyInt());很好,谢谢你抽出时间,再次非常感谢
    @Test
    public void findAll() {
    ...
    }
    
    @Test
    public void shouldGetCandidatesList() {
    ...
    }
    
    @Test
    public void shouldReturn404NotFoundWhenGetCandidateByIdAndItDoesntExist() {
    ...
    }
    
    @DeleteMapping("/candidates/{id}")
    public ResponseEntity<Void> deleteCandidate(@PathVariable int id) {
        try{
            candidateService.deleteById(id);
        } catch(ResourceNotFoundException e) {
           ResponseEntity.notFound().build()
        }
        return ResponseEntity.noContent().build();
    }
    
    @Test
    public void shouldReturnNotFoundWhenGetCandidateByNonExistingId() {
        //the Arrange part in your test 
        doThrow(new ResourceNotFoundException(candidate.getId())).when(candidateService).deleteById(anyInt());
    
        //call mockMvc 
    
        //assert not found using the MockMvcResultMatchers
    }
    
    @DeleteMapping("/candidates/{id}")
    public ResponseEntity<Void> deleteCandidate(@PathVariable int id) {
        try {
            candidateService.deleteById(id);
        } catch (ResourceNotFoundException e) {
           return ResponseEntity.notFound().build();
        }
        return ResponseEntity.noContent().build();
    
    }
    
    @Test
    public void shouldDeleteCandidate() throws Exception {
    
        Candidate candidate = getATestCandidate();
    
        doNothing().when(candidateService).deleteById(candidate.getId());
    
    
        mockMvc.perform(
                delete("/candidates/{id}", candidate.getId())
                        .contentType(MediaType.APPLICATION_JSON))
                        .andExpect(status().isNoContent());
    }
    
        Candidate candidate = getATestCandidate();
    
        doNothing().when(candidateService).deleteById(anyInt());
    
        mockMvc.perform(
                delete("/candidates/{id}", candidate.getId())
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isNoContent());
    
    }