Sunday, May 28, 2017

Spring Boot Unit Test error - Could not instantiate bean class [org.springframework.data.domain.Pageable]: Specified class is an interface

Below is an example with a rest web service using Spring Data, using a Pageable parameter (vs explicit @RequestParam's use of page, size, and sort parameters).  During unit testing of the controller method, I kept getting the "Could not instantiate bean class [org.springframework.data.domain.Pageable]: Specified class is an interface" error, and took some time to figure out how to make this work.  To make this work for the uint test, I chose a default Pageable object.  Below are my notes.

Environment:
Windows 7
Oracle Java 1.8.0_66
Spring Boot 1.3.1.RELEASE

Controller:
@RestController
@EnableAutoConfiguration
@CrossOrigin
public class CherryShoeController {

 
@Autowired
  private CherryShoeService cherryshoeService;

  // Use a @PageableDefault, for defaults if UI call does not supply it.  Also helps in unit testing to set Pageable values
  @RequestMapping(value = "/cherryshoe/data/listing", method = RequestMethod.GET,
     produces = "application/json;charset=utf-8")
  public Page<CherryShoeCustom> cherryshoeListing(
   @PageableDefault(page = 0, size = 10, sort = "id", direction = Sort.Direction.ASC) Pageable pageRequest,
   Principal principal) throws Exception
  {

    Page<CherryShoeCustom> pageListing = cherryshoeService.getDataPage(null,
     pageRequest.getPageNumber(), pageRequest.getPageSize(), pageRequest.getSort());
  
    return pageListing;
  }
 
 ...
}

Controller Unit Test:
public class CherryShoeControllerUnitTest {

  @InjectMocks
  private CherryShoeController unit;
 
  @Mock
  private Authentication mockAuthentication;
 
  @Mock
  private CherryShoeService mockCherryShoeService;
 
  private MockMvc mockMvc;
 
  @Before
  public void setup() throws Exception {
    // Process mock annotations
    MockitoAnnotations.initMocks(this);

    mockMvc = MockMvcBuilders.standaloneSetup(unit).build();
  }
 
  @Test
  public void cherryshoeListing_HappyTest() throws Exception {
    // *********************************************************
    // override mockMvc setup for paging support
    // Reference:
    // http://stackoverflow.com/questions/22174665/isolated-controller-test-cant-instantiate-pageable
    // The problem with pageable can be solved by providing a custom argument handler.
    // If this is set you will run in a ViewResolver Exception (loop). To avoid this you
    // have to set a ViewResolver (an anonymous JSON ViewResolver class for example).

    // Made this match the @PageableDefault default in the controller method
    Sort customSort = new Sort(Direction.ASC, "id");
    PageRequest customPageRequest = new PageRequest(0,10, customSort);
  
    mockMvc = MockMvcBuilders.standaloneSetup(unit)
             .setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver())
             .setViewResolvers(new ViewResolver() {
                 @Override
                 public View resolveViewName(String viewName, Locale locale) throws Exception {
                     return new MappingJackson2JsonView();
                 }
             })
             .build();
    // *********************************************************
  
    String fiscalYear = "2017";

    List<CherryShoeCustom> wrapperList = new ArrayList<CherryShoeCustom>();
    CherryShoeCustom wrapper = new CherryShoeCustom();
    wrapper.setFiscalYear(fiscalYear);
    wrapperList.add(wrapper);
    Page<CherryShoeCustom> pageList = new PageImpl<>(wrapperList);
  
    // define expectations on methods called by mocks inside the method
    // under test 
    when(mockCherryShoeService.getDataPage(customPageRequest.getPageNumber(),
      customPageRequest.getPageSize(), customPageRequest.getSort())).
      thenReturn(pageList);
  
    MvcResult result = mockMvc.perform(get("/cherryshoe/data/listing")
      .principal(mockAuthentication))
      .andReturn();

    System.out.println("results: " + result.getResponse().getContentAsString());

    // results are in the Page<T> data model format, I have a Page<CherryShoeCustom> data returning back
    // so modeled it in CherryShoeCustomPaging
    CherryShoeCustomPaging pagingReturn =
      objectMapper.readValue(result.getResponse().getContentAsString(), CherryShoeCustomPaging.class);
    System.out.println("****************" + pagingReturn);

    // verify method called once
    verify(mockCherryShoeService).getDataPage(customPageRequest.getPageNumber(),
      customPageRequest.getPageSize(), customPageRequest.getSort());
  
    // assertions
    assertEquals(HttpStatus.OK.value(), result.getResponse().getStatus());
    assertEquals(pagingReturn.getContent().get(0).getFiscalYear(), fiscalYear);
 }

}

CherryShoeCustomPaging: Data model returned from the cherryshoeService.getDataPage call (which called a repository with a custom query)
public class CherryShoeCustomPaging extends BasePaging {
  List<CherryShoeCustom> content;

  public List<CherryShoeCustom> getContent() {
    return content;
  }

  public void setContent(List<CherryShoeCustom> content) {
    this.content = content;
  }
 
  ...
}

BasePaging: Base data model returned from all Page<T> return types
public class BasePaging {
  private Integer totalElements;
  private Integer totalPages;
  private Boolean last;
  private Boolean size;
  private Boolean number;
  private List<BaseSorting> sort;
  private Boolean numberOfElements;
  private Boolean first;
 
  ...
}

BaseSorting: Sorting data model returned as a part of Page<T> return types
public class BaseSorting {
  private String direction;
  private String property;
  private Boolean ignoreCase;
  private String nullHandling;
  private Boolean ascending;
 
  ...
}

Helpful Links:
https://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-part-seven-pagination
http://stackoverflow.com/questions/22174665/isolated-controller-test-cant-instantiate-pageable

2 comments:

I appreciate your time in leaving a comment!