spring-mvcjunitspring-data-jpapaginationmockito

Unit Testing Spring MVC Controller that returns PagedResources


I am building an application with Spring Boot 1.4.1, Spring Data Jpa and Spring Data Test.

I have the following controller with which I want to return paged accounts:

@RequestMapping(method=RequestMethod.GET)
public HttpEntity<PagedResources<Account>> getAccounts(
        Pageable pageable, 
        PagedResourcesAssembler assembler,
        @RequestParam(value="name", required = false) String name, 
        @RequestParam(value="username", required = false) String username, 
        @RequestParam(value="email", required = false) String email, 
        @RequestParam(value="lastName", required = false) String lastName,
        @RequestParam(value="size", required = true, defaultValue = "10") Integer size,
        @RequestParam(value="page", required = true, defaultValue = "0") int page,
        @RequestParam(value="sort", required = false, defaultValue = "username") String sort,
        @RequestParam(value="direction", required = false, defaultValue = "asc") String direction,
        UriComponentsBuilder uriBuilder,
        HttpServletRequest request,
        HttpServletResponse response) {

    // form page request
    AccountList list = null;
    Page<Account> resultPage = null;
    Direction sortDirection = Direction.ASC;
    if(direction.equals("desc")) {
        sortDirection = Direction.DESC;
    }
    PageRequest pReq = new PageRequest(page, size, sortDirection, sort);

    resultPage = accountService.findAll(pReq);

    return new ResponseEntity<>(assembler.toResource(resultPage), HttpStatus.OK);
}

My test method is:

@Test
public void getAccountsTest() throws Exception {
    String uri = uriBase + "/accounts";

    List<Account> list = new ArrayList<Account>();
    list.add(accountOne);
    list.add(accountTwo);

    Page<Account> returnPage = new PageImpl<Account>(list, new PageRequest(0,10), list.size());

    when(accountService.findAll(any(PageRequest.class))).thenReturn(returnPage);

    mockMvc.perform(get(uri))
            .andExpect(status().isOk())
            .andDo(print())
            .andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
            .andExpect(jsonPath("$.accounts", hasSize(2)))
            .andExpect(jsonPath("$.accounts[*].username", 
                    hasItems(endsWith(accountOne.getUsername()), endsWith(accountTwo.getUsername()))))
            .andExpect(jsonPath("$.accounts[*].email", 
                    hasItems(endsWith(accountOne.getEmail()), endsWith(accountTwo.getEmail()))));
}

My problem is, that when I run the JUnit test it breaks with the error

org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.domain.Pageable]: Specified class is an interface
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:99)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.createAttribute(ModelAttributeMethodProcessor.java:142)
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.createAttribute(ServletModelAttributeMethodProcessor.java:80)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:102)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:114)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:65)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:155)
at de.campuz.platform.rest.controller.AccountControllerTestDoc.getAccountsTest(AccountControllerTestDoc.java:93)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.restdocs.JUnitRestDocumentation$1.evaluate(JUnitRestDocumentation.java:55)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

I added the @EnableSpringDataWebSupport annotation to my web config but the error still persists. When running the application as is the controller works just fine, I'll get the paged accounts as the JSON response. But I can't figure out how I can test my Controllers.

Does anyone have an idea how to fix this specific problem or how to unit test Spring MVC controllers which return paged content in general?


Solution

  • I solved it by removing the Pageable pageable from the Controller constructor, because I didn't need it anyway. Then the PagedResourcesAssembler assembler threw the error

    org.springframework.beans.BeanInstantiationException: 
    Failed to instantiate [org.springframework.data.web.PagedResourcesAssembler]: 
    No default constructor found; nested exception is java.lang.NoSuchMethodException: 
    org.springframework.data.web.PagedResourcesAssembler.<init>()
    

    I digged a bit into the topic of the ResourceAssembler and came up with the following solution:

    Controller:

    @RequestMapping(method=RequestMethod.GET)
    public ResponseEntity<PagedResources<AccountResource>> getAccounts(
            @RequestParam(value="name", required = false) String name, 
            @RequestParam(value="username", required = false) String username, 
            @RequestParam(value="email", required = false) String email, 
            @RequestParam(value="lastName", required = false) String lastName,
            @RequestParam(value="size", required = true, defaultValue = "10") Integer size,
            @RequestParam(value="page", required = true, defaultValue = "0") int page,
            @RequestParam(value="sort", required = false, defaultValue = "username") String sort,
            @RequestParam(value="direction", required = false, defaultValue = "asc") String direction,
            UriComponentsBuilder uriBuilder,
            HttpServletRequest request, 
            HttpServletResponse response) {
    
        // Build page request
        AccountList list = null;
        Page<Account> resultPage = null;
        Direction sortDirection = Direction.ASC;
        if(direction.equals("desc")) {
            sortDirection = Direction.DESC;
        }
        PageRequest pReq = new PageRequest(page, size, sortDirection, sort);
    
        resultPage = accountService.findAll(pReq);
    
        HateoasPageableHandlerMethodArgumentResolver resolver = new HateoasPageableHandlerMethodArgumentResolver();
        PagedResourcesAssembler<Account> accountPageAssembler = new PagedResourcesAssembler<Account>(resolver, null);
        return new ResponseEntity<PagedResources<AccountResource>>(accountPageAssembler.toResource(resultPage, new AccountResourceAsm()), HttpStatus.OK);
    }
    

    Where I create the Resource Assembler with the HateoasPageableHandlerMethodArgumentResolver to transform the Page to the PagedResource I wanted to return.

    Then I could finally run the controller test (as seen in the question) successfully.

    This answer helped me a lot, thanks Oliver Gierke and Patrick Cornelissen.