Spring WebFluxTest add Pageable resolver - spring

I want to test my Spring controller using #WebFluxTest(MyController.class)
My test :
#WebFluxTest({MyController.class})
class MyControllerTest {
#MockBean
private MyService myService;
#Autowired
private WebTestClient client;
#Test
void myTest() {
List<Integer> list = List.of(1,2,3);
Page<Integer> expected = new PageImpl<>(list);
String buCode = TestUtils.getRandomString();
when(myService.method(
any(), // Pageable
anyString()
)).thenReturn(Mono.just(expected));
client.get()
.uri("/path?arg=test")
.exchange()
.expectStatus()
.isOk();
}
}
My controller
#RestController
#RequestMapping("path")
#AllArgsConstructor
public class MyController {
private MyService myService;
#GetMapping
Mono<Page<Integer>> searchSeller(
Pageable pageable,
#RequestParam(required = false) String arg
) {
return myService.search(pageable, arg);
}
}
But I have this exception when I execute my test :
No primary or default constructor found for interface org.springframework.data.domain.Pageable
I think the solution is maybe to register the org.springframework.data.web.ReactivePageableHandlerMethodArgumentResolver but I don't know how to register it into the WebTestClient
Have you some clues to fix this issue ?

Related

Mockito Page<> test return null

I'm testing controller using mockito. Even though I stubbed about the getBoardList, It doesn't initiate the method.
This is the controller. getBoardList() doesn't initiate when I checked in debug mode.
#GetMapping
public String getBoardListView(#Valid #Nullable BoardDto.Request request,
#PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.ASC) Pageable pageable,
ModelMap map) {
Page<BoardDto.Response> boardList = postService.getBoardList(request, pageable);
map.addAttribute("boardList", boardList);
return "board/index";
}
This is the controllerTest
#MockBean private PostService postService;
#Test
void getBoardListView() throws Exception {
Page<BoardDto.Response> mock = Mockito.mock(Page.class);
when(postService.getBoardList(eq(null), any(Pageable.class))).thenReturn(mock);
mockMvc.perform(get("/board").with(csrf()))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
.andExpect(model().attributeExists("boardList"))
.andExpect(view().name("board/index"));
then(postService).should().getBoardList(any(BoardDto.Request.class), any(Pageable.class));
}
This is PostService interface.
public interface PostService {
Page<BoardDto.Response> getBoardList(BoardDto.Request request, Pageable pageable);
}
This is PostServiceImpl
#RequiredArgsConstructor
#Transactional(readOnly = true)
#Service
public class PostServiceImpl implements PostService {
private final PostRepository postRepository;
#Override
public Page<BoardDto.Response> getBoardList(BoardDto.Request request, Pageable pageable) {
return postRepository.findBoardList(request, pageable).map(BoardDto.Response::from);
}
}
Instead of:
when(postService.getBoardList(eq(null) ...
try:
when(postService.getBoardList(any(BoardDto.Request.class)
If you want to match a null argument, use ArgumentMatchers#isNull, not eq(null):
when(postService.getBoardList(isNull(), any(Pageable.class))).thenReturn(mock);

#MockBean with Junit Jupiter concurrent mode

I am trying to use
junit.jupiter.execution.parallel.mode.default=concurrent
along with #MockBean from Spring Boot. However, the tests starts to fail when I set to concurrent mode. I tried setting
#MockBean(reset = MockReset.NONE)
However, it also does not help. Seems like mocked bean is reinitialized / reset even though MockReset.NONE is set.
Is that possible to use #MockBean allow with concurrent mode or is it a known limitation?
Bean I am mocking:
#Service
public class SampleService {
public Mono<String> processCall(String call) {
return Mono.just("ok");
}
}
The controller I am testing:
#RestController
#RequiredArgsConstructor
public class SampleController {
private final SampleService sampleService;
#PostMapping("/call")
public Mono<String> processCall(#RequestBody String body) {
return sampleService.processCall(body);
}
}
And tests:
#ExtendWith(SpringExtension.class)
#WebFluxTest(SampleController.class)
#ContextConfiguration(classes = {SampleController.class})
class SampleTest {
#MockBean(reset = MockReset.NONE)
private SampleService sampleService;
#Autowired
private WebTestClient webTestClient;
#Test
void givenSampleServiceWorksFineExpectOkResponse() {
String randomBody = "1234";
when(sampleService.processCall(randomBody)).thenReturn(Mono.just("ok"));
callService(randomBody).expectStatus().isOk().expectBody(String.class).isEqualTo("ok");
}
#Test
void givenSampleServiceFailedExpectErrorResponse() {
String randomBody = "9876";
when(sampleService.processCall(randomBody))
.thenReturn(Mono.error(new RuntimeException("error")));
callService(randomBody).expectStatus().is5xxServerError();
}
private ResponseSpec callService(String body) {
return webTestClient
.post()
.uri(
uriBuilder ->
uriBuilder
.path("/call")
.build())
.bodyValue(body)
.exchange();
}
}
I set concurrent mode for Jupiter in file junit-platform.properties:
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.strategy=dynamic

How to mock Feign Client

I can't mock my Feign Client using Mockito.
MyService.class
#Service
#AllArgsConstructor
public class MyService implements IMyService{
private static final Logger LOGGER = LoggerFactory.getLogger(MyService .class);
private final MyRepository repository;
private final MyFeignClient myFeignClient;
#Autowired
private MyDao myDao;
#Override
#Async
public void process(Map<UUID, Long> command) {
DocIds docIds = getDocIds(command.values().stream().findFirst().get());
archiveData(command.keySet().stream().findFirst().get(), documentIds.getIds());
}
private DocumentIds getDocIds(Long retentionId) {
return myFeignClient.getDocumentIds(retentionId);
}
private void archiveData(UUID execId, List<Long> docIds) {
List<MyDocument> myDocs= prepareMyDocs(docIds);
repository.saveAll(myDocs);
}
And my test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class ArchiveServiceTest {
#Autowired
ArchiveService archiveService;
#MockBean
MyDao myDao;
#MockBean
DocRepository archiveRepository;
#MockBean
private MyFeignClient myFeignClient;
#Test
public void shouldReturnTheSameNumberOfDocumentsToArchive() {
//given
List<DocData> documentData = prepareDocumentData();
// doReturn(new DocIds()).when(myFeignClient).getDocumentIds(1000L);
DocumentIds documentIds = new DocumentIds();
documentIds.setIds(Arrays.asList(1L, 2L));
when(myFeignClient.getDocIds(any())).thenReturn(documentIds);
when(documentDataDao.getAllDocumentData(anyList())).thenReturn(documentData);
doNothing().when(archiveRepository.saveAll(any()));
//when
Map<UUID, Long> command = new HashMap<>();
command.put(UUID.randomUUID(), 1000L);
archiveService.process(command);
//then
...
MyFeignClient:
#FeignClient(name = "myFeignClient", url = "${feign.searcher.path}")
public interface MyFeignClient{
#RequestMapping(method = RequestMethod.GET, path = "/document/path/{id}")
DocIds getDocumentIds(#PathVariable("id") Long id);
}
When running a test,
return myFeignClient.getDocumentIds(retentionId);
returns NULL. Why?
I don't have more ideas. I don't want to use WireMock. The same happens with my documentDataDao that doesn't return any values (null) specified in thenReturn() clause.
Have You tried it this way:
Mockito.when(myFeignClient.getDocumentIds(Mockito.eq(1000L))).thenReturn(new DocIds());
In You example, mock is commented out ;)
// doReturn(new DocIds()).when(myFeignClient).getDocumentIds(1000L);
But I'm sure it is just a bug in Your example.

Using #RestClientTest in spring boot test

I want to write a simple test using #RestClientTest for the component below (NOTE: I can do it without using #RestClientTest and mocking dependent beans which works fine.).
#Slf4j
#Component
#RequiredArgsConstructor
public class NotificationSender {
private final ApplicationSettings settings;
private final RestTemplate restTemplate;
public ResponseEntity<String> sendNotification(UserNotification userNotification)
throws URISyntaxException {
// Some modifications to request message as required
return restTemplate.exchange(new RequestEntity<>(userNotification, HttpMethod.POST, new URI(settings.getNotificationUrl())), String.class);
}
}
And the test;
#RunWith(SpringRunner.class)
#RestClientTest(NotificationSender.class)
#ActiveProfiles("local-test")
public class NotificationSenderTest {
#MockBean
private ApplicationSettings settings;
#Autowired
private MockRestServiceServer server;
#Autowired
private NotificationSender messageSender;
#Test
public void testSendNotification() throws Exception {
String url = "/test/notification";
UserNotification userNotification = buildDummyUserNotification();
when(settings.getNotificationUrl()).thenReturn(url);
this.server.expect(requestTo(url)).andRespond(withSuccess());
ResponseEntity<String> response = messageSender.sendNotification(userNotification );
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
private UserNotification buildDummyUserNotification() {
// Build and return a sample message
}
}
But i get error that No qualifying bean of type 'org.springframework.web.client.RestTemplate' available. Which is right of course as i havn't mocked it or used #ContextConfiguration to load it.
Isn't #RestClientTest configures a RestTemplate? or i have understood it wrong?
Found it! Since i was using a bean that has a RestTemplate injected directly, we have to add #AutoConfigureWebClient(registerRestTemplate = true) to the test which solves this.
This was in the javadoc of #RestClientTest which i seem to have ignored previously.
Test which succeeds;
#RunWith(SpringRunner.class)
#RestClientTest(NotificationSender.class)
#ActiveProfiles("local-test")
#AutoConfigureWebClient(registerRestTemplate = true)
public class NotificationSenderTest {
#MockBean
private ApplicationSettings settings;
#Autowired
private MockRestServiceServer server;
#Autowired
private NotificationSender messageSender;
#Test
public void testSendNotification() throws Exception {
String url = "/test/notification";
UserNotification userNotification = buildDummyUserNotification();
when(settings.getNotificationUrl()).thenReturn(url);
this.server.expect(requestTo(url)).andRespond(withSuccess());
ResponseEntity<String> response = messageSender.sendNotification(userNotification );
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
private UserNotification buildDummyUserNotification() {
// Build and return a sample message
}
}

Spring boot testing error: java.lang.IllegalArgumentException: Page must not be null

I am trying to test the following controller:
#RepositoryRestController
#RequestMapping("movies")
public class MovieController {
private MovieService movieService;
private PagedResourcesAssembler<Movie> pagedAssembler;
private MovieResourceAssembler movieResourceAssembler;
#Autowired
public void setMovieService(MovieService movieService) {
this.movieService = movieService;
}
#Autowired
public void setPagedAssembler(PagedResourcesAssembler<Movie> pagedAssembler) {
this.pagedAssembler = pagedAssembler;
}
#Autowired
public void setMovieResourceAssembler(MovieResourceAssembler movieResourceAssembler) {
this.movieResourceAssembler = movieResourceAssembler;
}
// Return all movies with pagination
#GetMapping
public ResponseEntity<?> getAllMovies(Pageable pageable) {
Page<Movie> moviePage = this.movieService.getAllMovies(pageable);
// Remove some unnecessary fields
//this.movieService.removeUndesirableFieldsFromListing(moviePage);
return ResponseEntity.ok(this.pagedAssembler.toResource(moviePage, this.movieResourceAssembler));
}
}
and here's the test:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class MovieControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private MovieService movieService;
#Test
public void getAllMovies_PageableGiven_ShouldReturnMoviesPage() throws Exception {
List<Movie> movieList = new ArrayList<>();
movieList.add(new Movie());
movieList.add(new Movie());
Page<Movie> moviePage = new PageImpl<>(movieList);
given(this.movieService.getAllMovies(PageRequest.of(0,2)))
.willReturn(moviePage);
this.mockMvc.perform(get("http://localhost:8080/api/movies?page=1"))
.andExpect(status().isOk());
}
}
i got the following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: Page must not be null!
You could use Spring's mockMvc object injecting it inside your test class:
#Autowired
private MockMvc mockMvc;
I have just created a test method using mockMvc.perform method sending a page request:
My controller code:
#GetMapping(produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
public List<BaseResponse> listAllBase(
#PageableDefault(size = 50, page = 2) Pageable pageable) {
// logger.debug("paginación recibida :{}",pageable);
List<BaseResponse> findAllBases = baseService.findAllBases();
return findAllBases;
}
My test code:
mockMvc.perform(get("/base/?size=2&page=0")).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("$", hasSize(2))) .andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].name", equalToIgnoringCase("margarita")))
See full Class code in my GitHub repo:
Controller:
https://github.com/cristianprofile/spring-boot-mvc-complete-example/blob/develop/spring-boot-mvc-rest/src/main/java/com/mylab/cromero/controller/HelloWorldController.java#L53
Test class method:
https://github.com/cristianprofile/spring-boot-mvc-complete-example/blob/develop/spring-boot-mvc-rest/src/test/java/com/mylab/cromero/controller/RestTestIT.java#L66
Feel free to use it in your project :)
By reading these two Stackoverflow threads ([1] and [2]) and also Spring documentation, it seems that you should use Page with repositories. Example:
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));
For your case, it's recommended to use PagedListHolder, instead. Example:
List<Movie> movieList = new ArrayList<>();
movieList.add(new Movie());
movieList.add(new Movie());
PagedListHolder<Movie> holder = new PagedListHolder<>(movieList);

Resources