Rest API控制器的单元测试

时间:2019-04-10 14:51:51

标签: 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
    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();
    }

这是我的服务:


@Service
public class CandidateServiceImpl implements CandidateService {

    private CandidateRepository candidateRepository;
    private static final Logger log = LoggerFactory.getLogger(CandidateServiceImpl.class);

    public CandidateServiceImpl() {

    }

    @Autowired
    public CandidateServiceImpl(CandidateRepository repository) {
        this.candidateRepository = repository;
    }

    @Override
    public List<Candidate> findAll() {
        List<Candidate> list = new ArrayList<>();
        candidateRepository.findAll().forEach(e -> list.add(e));
        return list;
    }

    @Override
    public Candidate findById(int id) {
        Candidate candidate = candidateRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException(id));
        return candidate;
    }

    @Override
    public Candidate findBySocialNumber(int number) {
        Candidate candidate = candidateRepository.findBySocialNumber(number).orElse(null);
        return candidate;
    }

    @Override
    public List<Candidate> findByLastNameLike(String userName) {
        return candidateRepository.findByLastNameLike(userName).orElseThrow(() -> new ResourceNotFoundException(0, "No result matches candidates with name like : " + userName));
    }

    @Override
    public Candidate save(Candidate candidate) {
        Candidate duplicateCandidate = this.findBySocialNumber(candidate.getSocialNumber());
        if (duplicateCandidate != null) { // Candidat existant avec numéro sécuAucun Candidat avec ce numéro sécu
            log.info("Candidate with username = {} found in database", candidate.getSocialNumber());
            throw new ResourceAlreadyExistException("Social security number : " + (candidate.getSocialNumber()));
        }
        log.info("Candidate with social number = {} found in database", candidate.getSocialNumber());
        return candidateRepository.save(candidate);
    }

    @Override
    public void update(Candidate candidate, int id) {
        log.info("Candidate to be updated : id = {}", candidate.getId());
        Candidate candidateFromDb = this.findById(id);
        if (candidateFromDb != null) {
            // Candidate présent => update
            candidate.setId(id);
            candidateRepository.save(candidate);
        } else {
            // Candidate absent => no update
            log.info("Candidate with id = {} cannot found in the database", candidate.getId());
            throw new ResourceNotFoundException(id);
        }
    }


    @Override
    public void deleteById(int id) {
        Candidate candidate = this.findById(id);
        if (candidate != null) {
            candidateRepository.delete(candidate);
        } else {
            throw new ResourceNotFoundException(id);
        }
    }
}

我的测试文件:

@RunWith(SpringRunner.class)
@WebMvcTest(value = CandidateController.class, secure = false)
public class CandidateControllerTestMockito {


    //parse date to use it in filling Candidate model
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    String dateString = format.format(new Date());
    Date date = format.parse("2009-12-31");


    static private List<Candidate> candidates = new ArrayList<>();


    static Candidate candidate = new Candidate();
    {
        candidate.setId(1);
        candidate.setLastName("pierre");
        candidate.setFirstName("pust");
        candidate.setBirthDate(date);
        candidate.setNationality("testFrancaise");
        candidate.setBirthPlace("testParis");
        candidate.setBirthDepartment("test92");
        candidate.setGender("testMale");
        candidate.setSocialNumber(1234);
        candidate.setCategory("testCategory");
        candidate.setStatus("testStatus");
        candidate.setGrade("testGrade");
        candidate.setFixedSalary(500);
        candidate.setPrivatePhoneNumber(0707070707);
        candidate.setPrivateEmail("test@ALEX.com");
        candidate.setPosition("testPosition");
        candidate.setStartingDate(date);
        candidate.setSignatureDate(date);
        candidate.setContractStatus("testContractStatus");
        candidate.setContractEndDate("testContractEnd");
        candidate.setIdBusinessManager(1);
        candidate.setIdAdress(12);
        candidate.setIdMissionOrder(11);

        candidates.add(candidate);
    }



    @Autowired
    private MockMvc mockMvc;


    @MockBean
    private CandidateService candidateService;


    public CandidateControllerTestMockito() throws ParseException {
    }




    @Test
    public void findAll() throws Exception {

        when(
                candidateService.findAll()).thenReturn(candidates);


        RequestBuilder requestBuilder = get(
                "/candidates").accept(
                MediaType.APPLICATION_JSON);

        MvcResult result = mockMvc.perform(requestBuilder).andReturn();

        System.out.println("ici"+candidates.toString());

        String expected = "[{\"lastName\":\"pierre\",\"firstName\":\"pust\",\"birthDate\":1262214000000,\"nationality\":\"testFrancaise\",\"birthPlace\":\"testParis\",\"birthDepartment\":\"test92\",\"gender\":\"testMale\",\"socialNumber\":1234,\"category\":\"testCategory\",\"status\":\"testStatus\",\"grade\":\"testGrade\",\"fixedSalary\":500.0,\"privatePhoneNumber\":119304647,\"privateEmail\":\"test@ALEX.com\",\"position\":\"testPosition\",\"schoolYear\":null,\"startingDate\":1262214000000,\"signatureDate\":1262214000000,\"contractStatus\":\"testContractStatus\",\"contractEndDate\":\"testContractEnd\",\"idBusinessManager\":1,\"idAdress\":12,\"idMissionOrder\":11}]";


        JSONAssert.assertEquals(expected, result.getResponse()
               .getContentAsString(), false);
    }



    @Test
    public void findByIdOk() throws Exception {

        when(candidateService.findById(candidate.getId())).thenReturn(candidate);
        Candidate cand=candidateService.findById(candidate.getId());
        int idCand=cand.getId();
        assertEquals(idCand,1);

        RequestBuilder requestBuilder = get(
                "/candidates/1").accept(
                MediaType.APPLICATION_JSON);

        MvcResult result = mockMvc.perform(requestBuilder).andReturn();

        MockHttpServletResponse response = result.getResponse();
        assertEquals(HttpStatus.OK.value(), response.getStatus());

    }

    @Test
    public void findByIdFail() throws Exception {

        when(candidateService.findById(18)).thenReturn(null);


        RequestBuilder requestBuilder = get(
                "/candidates/18").accept(
                MediaType.APPLICATION_JSON);

        MvcResult result = mockMvc.perform(requestBuilder).andReturn();

        MockHttpServletResponse response = result.getResponse();
        assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatus());

    }





    @Test
    public void deleteCandidate() throws Exception{

        when(candidateService.findById(candidate.getId())).thenReturn(candidate);
        doNothing().when(candidateService).deleteById(candidate.getId());

        mockMvc.perform(
                delete("/candidates/{id}", candidate.getId()))
                .andExpect(status().isNoContent());

    }



我问我是不是做对了? 我想对deleteCandidateDontExist做一个测试 我尝试过:

when(candidateService.findById(candidate.getId())).thenReturn(null);
        doNothing().when(candidateService).deleteById(candidate.getId());
 mockMvc.perform(...


我期望没有找到 404 的答复,但我得到的答复是 204 没有内容!

3 个答案:

答案 0 :(得分:1)

我会尽力为您提供一些指导,以帮助您:

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

  2. 使用Spring MVC测试框架,您可以检查整个端点基础结构,包括HTTP状态代码,输入和输出序列化,响应主体,重定向等。不要通过测试无关的内容来偏离它们:在findByIdOk()测试的第一部分中,您正在测试自己的Mock。

 4. when(candidateService.findById(candidate.getId())).thenReturn(candidate);
 5. Candidate cand=candidateService.findById(candidate.getId());
 6. int idCand=cand.getId();
 7. assertEquals(idCand,1);

别忘了单元测试的基本AAA概念(安排,行为,声明)也适用于MVC测试。这应该是测试的安排部分,在该过程中,您将设置控制器collaborator(candidateService)以在被ID调用时返回候选者。第一行很好,但是调用它并确保id为1是没有用的,因为您指示了模拟返回该候选者,现在您测试它是否正在返回它(您应该信任它的Mockito)=>删除第2、3行和4从findByIdOk()。

findByIdOk()测试方法的另一项改进是使用Mock MVC流利API来检查您的状态和响应内容。

因此,您通过id查找方法可能会变成(检查点3,以了解为什么我重命名了id):

@Test
public void shouldReturnCandidateById() throws Exception {
    //ARRANGE
    Candidate candidate = getATestCandidate();
    when(candidateService.findById(candidate.getId())).thenReturn(candidate);
    RequestBuilder requestBuilder = get(
           "/candidates/" + candidate.getId()).accept(
            MediaType.APPLICATION_JSON);

    //ACT 
    MvcResult result = mockMvc.perform(requestBuilder).
    //ASSERT
                           .andExpect(status().is(200))
                           .andExpect(jsonPath("$.id", is(candidate.getId())))
                           ...
                           //here you are checking whether your controller returns the
                           //correct JSON body representation of your Candidate resource 
                           //so I would do jsonPath checks for all the candidate fields
                           //which should be part of the response

}

与单独检查整个json主体相比,更希望分别检查带有json路径的json字段。

现在想想一下,当您已经模拟模拟协作者CandidateService指示返回ID为1的候选者(这并没有证明任何事情)时,测试该控制器与该控制器单元是否能够返回候选者之间的区别当查询特定的候选ID时,资源表示形式为JSON,其中包含所有候选字段。

  1. 因为对于同一个控制器端点,您可能会有多种测试方法,所以您需要以暗示性的方式为测试方法命名,以说明您要测试的内容。这样,您可以记录测试,并且测试也将变得可维护。以后其他人很容易弄清楚测试应该怎么做以及如果测试损坏了怎么解决。在整个应用程序中使用命名约定甚至是一个好习惯。

例如 在您特定的Test类中,而不是创建测试

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

创建一个更具暗示性的名称,其中还包括您要处理的资源

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

@Test
public void shouldReturn404NotFoundWhenGetCandidateByIdAndItDoesntExist() {
...
}
  1. 现在进入删除端点和服务实现。您可以将对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();
}

现在,您需要进行测试,以检查使用不存在的候选ID的删除终结点时控制器是否返回Not found。为此,您将在测试中指示模拟协作者(candidateService)在调用该ID时返回null。不要陷入对模拟候选人服务再次执行任何断言的陷阱。此测试的目的是确保使用不存在的候选ID调用时,端点返回NotFound。

您的shouldReturnNotFoundWhenGetCandidateByNonExistingId()测试框架

@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
}

请调整获取端点的测试,以检查JSON正文。拥有一个仅测试端点返回状态时的状态的测试,某些响应主体仅完成了一半。

也请查看有关如何构造端点的一些文档。您在这里所做的可能正在工作并可以编译,但这并不意味着它是正确的。我指的是这一点(“ /候选人/名称/ {名称}”,“ /候选人/创建”)。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

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

@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());
}

但应该返回404WhenDeleteCandidateDontExist不返回任何内容,并且我期待404 ..


@测试     公共无效值应该返回NoContentWhenDeleteCandidateDontExist()引发异常{

    Candidate candidate = getATestCandidate();

    doNothing().when(candidateService).deleteById(anyInt());

    mockMvc.perform(
            delete("/candidates/{id}", candidate.getId())
                    .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isNoContent());

}