0

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!

  • @GastónSchabas Yes, it worked!!! Thanks, whas stuk in this problem for like 4 hours. If you want to make that coment into an response I can mark it as a answear. – Carlos Eduardo Ilgenfritz Jun 02 '23 at 00:15
  • Thanks for the offer, but the idea is to avoid having [duplicated questions](https://stackoverflow.com/help/duplicates). The article explain very well the purpose of that – Gastón Schabas Jun 02 '23 at 00:26

0 Answers0