I'm trying to disable REDIS in the context of my integration tests, but I'm not having success.
Whenever I run the integration tests, SpringBoot starts saving the test data inside REDIS, which causes inconsistency and the tests fail.
I have the following classes:
GetDetailedBookDataIT contains the integration test
@RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)@ActiveProfiles("test")public class GetDetailedBookDataIT { @LocalServerPort private int port; @Autowired private BookRest bookRest; @Autowired private RestTemplate restTemplate; @Mock private GutendexHttpClient gutendexHttpClient; @InjectMocks private GetDetailedBookData getDetailedBookData; @Test public void it_should_get_detailed_book_data() { String saveBookurl = "http://localhost:" + port +"/books/save"; Integer bookId = 1; Integer rating = 3; BookReviewDto bookReviewDto = new BookReviewDtoBuilder().setBookId(bookId).setRating(rating).create(); String getDetailedBookDataApiUrl = "http://localhost:" + port +"/books/detailed?id=" + bookId; restTemplate.postForEntity(saveBookurl, bookReviewDto, String.class); ResponseEntity<DetailedBookDataDto> response = restTemplate.getForEntity(getDetailedBookDataApiUrl, DetailedBookDataDto.class); assertEquals(1, response.getBody().id); assertEquals("Book data retrieved successfully.", response.getBody().responseMessage); } @Test public void it_should_return_error_message_if_book_id_is_below_zero() { Integer bookId = -1; String getDetailedBookDataApiUrl = "http://localhost:" + port +"/books/detailed?id=" + bookId; try{ restTemplate.getForEntity(getDetailedBookDataApiUrl, DetailedBookDataDto.class); }catch (HttpClientErrorException e){ assertTrue(e.getMessage().contains("Book id cannot be negative or zero.")); assertEquals(HttpStatus.BAD_REQUEST, e.getStatusCode()); } } @Test public void it_should_return_error_message_if_book_id_is_zero() { Integer bookId = 0; String getDetailedBookDataApiUrl = "http://localhost:" + port +"/books/detailed?id=" + bookId; try{ restTemplate.getForEntity(getDetailedBookDataApiUrl, DetailedBookDataDto.class); }catch (HttpClientErrorException e){ assertTrue(e.getMessage().contains("Book id cannot be negative or zero.")); assertEquals(HttpStatus.BAD_REQUEST, e.getStatusCode()); } } @Test public void it_should_return_error_message_if_no_book_is_found_for_given_id() { Integer bookId = 5; String getDetailedBookDataApiUrl = "http://localhost:" + port +"/books/detailed?id=" + bookId; try{ restTemplate.getForEntity(getDetailedBookDataApiUrl, DetailedBookDataDto.class); }catch (HttpClientErrorException e){ assertTrue(e.getMessage().contains("No book review found for this book.")); assertEquals(HttpStatus.NOT_FOUND, e.getStatusCode()); } } @Test public void it_should_return_error_message_if_gutendex_api_is_unavaiable(){ openMocks(this); String saveBookurl = "http://localhost:" + port +"/books/save"; Integer bookId = 5; BookReviewDto bookReviewDto = new BookReviewDtoBuilder().setBookId(bookId).create(); String getDetailedBookDataApiUrl = "http://localhost:" + port +"/books/detailed?id=" + bookId; when(gutendexHttpClient.getBookBasedOnId(any())).thenThrow(new ResourceAccessException("Simulated Exception")); restTemplate.postForEntity(saveBookurl, bookReviewDto, String.class); try{ restTemplate.getForEntity(getDetailedBookDataApiUrl, DetailedBookDataDto.class); }catch (HttpClientErrorException e){ assertTrue(e.getMessage().contains("Guntendex API is not available.")); assertEquals(HttpStatus.REQUEST_TIMEOUT, e.getStatusCode()); } } @Test public void it_should_return_error_message_when_gutendex_api_returns_null_for_given_id(){ openMocks(this); String saveBookurl = "http://localhost:" + port +"/books/save"; Integer bookId = 5; BookReviewDto bookReviewDto = new BookReviewDtoBuilder().setBookId(bookId).create(); String getDetailedBookDataApiUrl = "http://localhost:" + port +"/books/detailed?id=" + bookId; when(gutendexHttpClient.getBookBasedOnId(any())).thenReturn(new GutendexSearchResultDtoBuilder().createWithBooksNull()); restTemplate.postForEntity(saveBookurl, bookReviewDto, String.class); try{ restTemplate.getForEntity(getDetailedBookDataApiUrl, DetailedBookDataDto.class); }catch (HttpClientErrorException e){ assertTrue(e.getMessage().contains("Book data not found in Gutendex API.")); assertEquals(HttpStatus.NOT_FOUND, e.getStatusCode()); } } @Test public void it_should_return_error_message_when_gutendex_api_returns_empty_list_for_given_id(){ openMocks(this); String saveBookurl = "http://localhost:" + port +"/books/save"; Integer bookId = 5; BookReviewDto bookReviewDto = new BookReviewDtoBuilder().setBookId(bookId).create(); String getDetailedBookDataApiUrl = "http://localhost:" + port +"/books/detailed?id=" + bookId; when(gutendexHttpClient.getBookBasedOnId(any())).thenReturn(new GutendexSearchResultDtoBuilder().createWithBooksEmpty()); restTemplate.postForEntity(saveBookurl, bookReviewDto, String.class); try{ restTemplate.getForEntity(getDetailedBookDataApiUrl, DetailedBookDataDto.class); }catch (HttpClientErrorException e){ assertTrue(e.getMessage().contains("Book data not found in Gutendex API.")); assertEquals(HttpStatus.NOT_FOUND, e.getStatusCode()); } } @Test public void it_should_return_error_message_when_unexpected_error_occurs(){ openMocks(this); String saveBookurl = "http://localhost:" + port +"/books/save"; Integer bookId = 5; BookReviewDto bookReviewDto = new BookReviewDtoBuilder().setBookId(bookId).create(); String getDetailedBookDataApiUrl = "http://localhost:" + port +"/books/detailed?id=" + bookId; when(gutendexHttpClient.getBookBasedOnId(any())).thenThrow(new RuntimeException("Simulated Exception")); restTemplate.postForEntity(saveBookurl, bookReviewDto, String.class); try{ restTemplate.getForEntity(getDetailedBookDataApiUrl, DetailedBookDataDto.class); }catch (HttpServerErrorException e){ assertTrue(e.getMessage().contains("An unexpected error occurred, please try again, " +"if the error persists contact support")); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, e.getStatusCode()); } }}
And then I have this service class that contains some logic
@Servicepublic class GetDetailedBookData { private BookReviewRepository bookReviewRepository; private GutendexHttpClient gutendexHttpClient; public GetDetailedBookData(BookReviewRepository bookReviewRepository, GutendexHttpClient gutendexHttpClient) { this.bookReviewRepository = bookReviewRepository; this.gutendexHttpClient = gutendexHttpClient; } @Cacheable(value = "detailedBook") public DetailedBookDataDto get(Integer bookId){ if(bookId == null){ return new DetailedBookDataDto("Book id is required.", HttpStatus.BAD_REQUEST); } else if(bookId <= 0){ return new DetailedBookDataDto("Book id cannot be negative or zero.", HttpStatus.BAD_REQUEST); } try{ List<BookReview> bookReviewList = bookReviewRepository.findByBookId(bookId); if(bookReviewList.isEmpty()){ return new DetailedBookDataDto("No book review found for this book.", HttpStatus.NOT_FOUND); } GutendexSearchResultDto bookBasedOnId; try { bookBasedOnId = gutendexHttpClient.getBookBasedOnId(bookId); }catch (ResourceAccessException e){ e.printStackTrace(); return new DetailedBookDataDto("Guntendex API is not available.", HttpStatus.REQUEST_TIMEOUT); } if(bookBasedOnId.books == null || bookBasedOnId.books.isEmpty()){ return new DetailedBookDataDto("Book data not found in Gutendex API.", HttpStatus.NOT_FOUND); } DetailedBookDataDto detailedBookDataDto = createDetailedBookDataDto(bookReviewList, bookBasedOnId); detailedBookDataDto.responseMessage = "Book data retrieved successfully."; detailedBookDataDto.httpStatus = HttpStatus.OK; return detailedBookDataDto; }catch (Exception e){ e.printStackTrace(); return new DetailedBookDataDto("An unexpected error occurred, please try again, " +"if the error persists contact support", HttpStatus.INTERNAL_SERVER_ERROR); } } private DetailedBookDataDto createDetailedBookDataDto(List<BookReview> bookReviewList, GutendexSearchResultDto bookBasedOnId) { Double avgRating = bookReviewList.stream() .mapToInt(BookReview::getRating) .average() .orElse(0); BigDecimal roundedAvgRating = new BigDecimal(avgRating).setScale(2, RoundingMode.HALF_UP); List<String> listOfReviews = bookReviewList.stream().map(BookReview::getReview).toList(); GutendexBookDto bookInformation = bookBasedOnId.books.get(0); DetailedBookDataDto detailedBookDataDto = new DetailedBookDataDto(bookInformation.id, bookInformation.title, bookInformation.authors, bookInformation.languages, bookInformation.download_count, roundedAvgRating.doubleValue(), listOfReviews); detailedBookDataDto.responseMessage = "Book data retrieved successfully."; return detailedBookDataDto; }}
How do I know that the problem with my tests is REDIS? Why when I delete the @Cacheable(value = "detailedBook")
line inside my service, the tests work again
I have two properties, one for testing and one for development.
The development properties:
# PostgreSQL connection propertiesspring.datasource.url=jdbc:postgresql://localhost:5432/postgresspring.datasource.username=postgresspring.datasource.password=postgresspring.datasource.driver-class-name=org.postgresql.Driverspring.jpa.hibernate.ddl-auto=createspring.jpa.show-sql=true#Redisspring.redis.host=localhostspring.redis.port=6380logging.level.org.springframework.data.redis=DEBUGspring.autoconfigure.exclude=org.springframework.boot.autoconfigure.data.redis.RedisAutoConfigurationspring.data.redis.repositories.enabled=falsegutendex.baseurl=https://gutendex.com/books
And the Test properties:
spring.datasource.url=jdbc:h2:mem:testdbspring.datasource.driverClassName=org.h2.Driverspring.datasource.username=saspring.datasource.password=passwordspring.jpa.database-platform=org.hibernate.dialect.H2Dialectspring.jpa.hibernate.ddl-auto=createspring.jpa.show-sql=truespring.h2.console.enabled=truespring.h2.console.path=/h2-consolespring.data.redis.repositories.enabled=falsegutendex.baseurl=https://gutendex.com/books
I've already tried:
- Make redis point to a non-existent address within the test properties
spring.redis.host=non-existing-host spring.redis.port=9999
- Disable Redis Auto-Configure within test properties
- Add this line of code to my class that contains the integration test
@TestPropertySource(properties = {"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration"})
And many other things I can't remember.
If anyone can help me I would be very grateful, thanks!