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
@Service
public 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 properties
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
#Redis
spring.redis.host=localhost
spring.redis.port=6380
logging.level.org.springframework.data.redis=DEBUG
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
spring.data.redis.repositories.enabled=false
gutendex.baseurl=https://gutendex.com/books
And the Test properties:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.data.redis.repositories.enabled=false
gutendex.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!