Hibernate Search in Spring Boot integration test problem - spring

I'm using Spring Boot 2.1.0.RELEASE and Hibernate Search 5.10.8.Final.
I have a problem with Hibernate Search in my integration test in Spring Boot.
Integration test
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
#TestPropertySource(locations = "classpath:application-test.properties")
public class FindBookE2ETest {
private String HTTP_LOCALHOST = "http://localhost:";
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
#Sql({"/book/book.sql"})
public void findBookByLanguageTest() {
ResponseEntity<List<BookDTO>> exchange = restTemplate.exchange(HTTP_LOCALHOST + port + "/findBooks/ENG/language", HttpMethod.GET, null, new ParameterizedTypeReference<List<BookDTO>>() {});
assertThat(exchange.getStatusCode().value()).isEqualTo(HttpStatus.OK.value());
assertThat(exchange.getBody()).isNotNull();
assertThat(exchange.getBody().get(0).getLanguage()).isEqualTo("ENG");
}
}
In classpath:application-test.properties I have only spring.datasource.initialization-mode=never
Implementation of Hibernate Search
#Transactional
#Service("bookService")
public class BookServiceImpl implements BookService {
#Autowired
private BookRepository bookRepository;
#PersistenceContext
private EntityManager entityManager;
private List<Book> searchBookByLanguage(String value) {
List<Book> booksByLanguages = bookRepository.findBooksByLanguages(value); //for debug test purpose
System.out.println("book from repo: " + booksByLanguages.size()); //for debug test purpose
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory()
.buildQueryBuilder()
.forEntity(Book.class)
.get();
Query luceneQuery = queryBuilder
.keyword()
.onFields("language")
.matching(value)
.createQuery();
FullTextQuery jpaQuery = fullTextEntityManager.createFullTextQuery(luceneQuery, Book.class);
List<Book> resultList = jpaQuery.getResultList();
return resultList;
}
///rest of the code
}
Under src/main/resource I have data.sql file. Under test/resource I have sql files for each tests.
My problem is that.
When I run whole application everything works ok, but in integration test no. In findBookByLanguageTest() hibernate search does not return any values (bookRepository.findBooksByLanguages() returns) and I do not why. Maybe there is configuration problem?
I commented spring.datasource.initialization-mode=never in test properties and I got error that sql script in my test can not insert data because (I do not know why) Spring get data from data.sql under src/main/reource/data.sql but this file is not in "test" section. So I commented sql annotation above my test as well so with this configuration:
#spring.datasource.initialization-mode=never in classpath:application-test.properties
and
#Test
// #Sql({"/book/book.sql"})
public void findBookByLanguageTest() {
ResponseEntity<List<BookDTO>> exchange = restTemplate.exchange(HTTP_LOCALHOST + port + "/findBooks/ENG/language", HttpMethod.GET, null, new ParameterizedTypeReference<List<BookDTO>>() {});
assertThat(exchange.getStatusCode().value()).isEqualTo(HttpStatus.OK.value());
assertThat(exchange.getBody()).isNotNull();
assertThat(exchange.getBody().get(0).getLanguage()).isEqualTo("ENG");
}
my test is working.
Someone knows what is the problem with my configuration? I want to separate my tests from data.sql and to have sql script for each test.
Maybe there is a problem with EntityManager?
**** Edit after #yrodiere response ****
I modified my FindBookE2ETest and now it looks like this
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
#TestPropertySource(locations = "classpath:application-test.properties")
public class FindBookE2ETest {
private String HTTP_LOCALHOST = "http://localhost:";
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private EntityManager entityManager;
#Before
public void setUp(){
entityManager = entityManager.getEntityManagerFactory().createEntityManager();
}
#Test
#Sql({"/book/book.sql"})
public void findBookByLanguageTest() {
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
fullTextEntityManager.createIndexer().startAndWait();
ResponseEntity<List<BookDTO>> exchange = restTemplate.exchange(HTTP_LOCALHOST + port + "/findBooks/ENG/language", HttpMethod.GET, null, new ParameterizedTypeReference<List<BookDTO>>() {});
assertThat(exchange.getStatusCode().value()).isEqualTo(HttpStatus.OK.value());
assertThat(exchange.getBody()).isNotNull();
assertThat(exchange.getBody().get(0).getLanguage()).isEqualTo("ENG");
}
}
Now everything is working. Thx.

Related

Why Testing Controller not working with #RunWith(SpringRunner.class) & #WebMvcTest annotation?

I am getting the below error when I try to run my controller test. Please can you let me know what I am missing? Why should I add the #ContextConfiguration or #SpringBootTest ?
java.lang.IllegalStateException: Unable to find a #SpringBootConfiguration, you need to use #ContextConfiguration or #SpringBootTest(classes=...) with your test
#Controller
public class BasketController {
#Autowired
private BasketService basketService;
#GetMapping(value="/baskets/{basketId}")
public ResponseEntity<BasketDto> getBasket(#PathVariable("basketId") UUID basketId){
Basket basket = basketService.getBasket(basketId);
BasketDto dto = toBasketDto(basket);
ResponseEntity response = ResponseEntity.status(HttpStatus.OK).body(dto);
return response;
}
private BasketDto toBasketDto(Basket basket){
BasketDto dto = new BasketDto(basket.getBasketId(), basket.getItems());
return dto;
}
}
#RunWith(SpringRunner.class)
#WebMvcTest(BasketController.class)
public class BasketControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BasketService basketService;
#Test
public void testGetItemsInBasketSuccessfully() throws Exception {
UUID basketId = UUID.randomUUID();
String URI = "/api/baskets/" + basketId;
Basket mockBasket = new Basket(basketId);
Mockito.when(basketService.getBasket(basketId)).thenReturn(mockBasket);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(URI).accept(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
String actualJson = result.getResponse().getContentAsString();
System.out.println(actualJson);
}
}

Why wont my Spring Boot unit test load #Value properties in the service class?

Why wont Spring Boot test load #Value properties in the service class? I have a service class with this property on it.
#Value("${azure.storage.connection-string}")
private String connectionString;
I am using JUnit4 . When this test runs, the connectionString property is null.
#TestPropertySource(locations="classpath:/application-test.yml")
#Slf4j
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
public class BlobServiceTest {
#Autowired
private ObjectMapper objectMapper;
private BlobService blobServiceSpy;
#Before
public void setup() {
blobServiceSpy = Mockito.spy(new BlobService(objectMapper));
}
#Test
public void testGetExampleBlob() {
String stubFileId = UUID.randomUUID().toString();
PdfBlob pdfBlob = null;
try {
pdfBlob = blobServiceSpy.downloadBlobContent(stubFileId);
} catch (JsonProcessingException e) {
log.error("Test failed.", e);
Assert.fail();
}
Assert.assertNotNull("PDF blob was null.", pdfBlob);
}
}
There is some kind of answer in this question, but it is very unclear, and the answer needs more info.
This worked but it seems like a "hack":
public class BlobServiceTest {
#Value("${azure.storage.containerName}")
private String containerName;
#Value("${azure.storage.connection-string}")
private String connectionString;
#Autowired
private ObjectMapper objectMapper;
private BlobService blobServiceSpy;
#Before
public void setup() {
blobServiceSpy = Mockito.spy(new BlobService(objectMapper));
//TODO not sure why, but had to setup connectionString property this way
blobServiceSpy.containerName = this.containerName;
blobServiceSpy.connectionString = this.connectionString;
}
Why doesn't SpringBootTest wire up those values for me when I create the Spy object?
When you use new to create your BlobService spring doesn't know about it an therefore doesn't inject your properties. Try to #Autowire your blob service and then wrap it in the spy.

How to mock RestTemplate in Springboot app

This question has already been asked. The accepted answer doesn't work for me. Here is my code:-
My service is here:
#Service
public class PlantService {
#Autowired
RestTemplate restTemplate;
static String url = "http://some_url_?Combined_Name=Oak";
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
public String getJson() {
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
return response.getBody();
}
}
My unit test
#RunWith(SpringRunner.class)
class PlantServiceTest {
private PlantService plantService;
#Mock
#Autowired
private RestTemplate restTemplate;
#Before
void setUp() {
MockitoAnnotations.initMocks(this);
plantService = new PlantService();
}
#Test
void testGetJsonString() {
// arrange
String expectedJson = "Some json string";
ResponseEntity mocResponse = mock(ResponseEntity.class);
// act
when(restTemplate.getForEntity("url", String.class)).thenReturn(mocResponse);
String actualJson = plantService.getJson();
// assert
assertSame(expectedJson, actualJson);
}
}
When I debug and step into the actual code. I can see restTemplate is null and throws java.lang.NullPointerException. So how do I unit test this code?
I have tried your code running on my machine.
Please find the test running test class
#RunWith(SpringRunner.class)
class PlantServiceTest {
#InjectMocks
private PlantService plantService;
#Mock
private RestTemplate restTemplate;
String url = "http://some_url_?Combined_Name=Oak";
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
void testGetJsonString() {
// arrange
String expectedJson = "Some json string";
ResponseEntity mocResponse = new ResponseEntity("Some json string", HttpStatus.OK);
// act
when(restTemplate.getForEntity(url, String.class)).thenReturn(mocResponse);
String actualJson = plantService.getJson();
// assert
assertSame(expectedJson, actualJson);
}
}
You can do the following:
Remove #RunWith annotation.
Annontate your test class with #RestClientTest from org.springframework.boot.test.autoconfigure.web.client.RestClientTest.
Use MockRestServiceServer from org.springframework.test.web.client.MockRestServiceServer.
Mock the response of the server when being called in the test method, example:
#RestClientTest
public class MyTest {
#Autowired
private MockRestServiceServer server;
public void test() {
// setup
String expected = "test_value";
server.expect(requestToUriTemplate("/myendpoint"))
.andRespond(withSuccess(myJsonResponse, MediaType.APPLICATION_JSON));
// act
String actual = myClient.fetch(myRequestDto);
// assert
assertThat(actual, equalTo(expected));
server.verify();
}
}
I'm using assertThat from hamcrest, you can use whatever you want to assert the correctness of the result.
You should use constructor injection in your PlantService. For instance:
public class PlantService {
RestTemplate restTemplate;
#Autowired
public PlantService(RestTemplate restTemplate){
this.restTemplate = restTemplate;
}
}
And on your test you can just do:
plantService = new PlantService(restTemplate);
^^
yourMock
Your problem is the plantService = new PlantService(); You never inject into this selft created instance.
Solution 1
I usually do it like this:
#InjectMocks
private PlantService plantService = new PlantService();
#Mock
private RestTemplate restTemplate;
Remove the setup method and run with Mockito instead of the SpringRunner.
Solution 2
If you need the SpringRunner you can do the following:
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
plantService = new PlantService();
ReflectionTestUtils.setField(plantService, "restTemplate", restTemplate);
}
As I've worked with JUnit 5 in the last years, I'm not sure about the SpringRunner. In JUnit 5 I can use both extensions (Spring and Mockito at the same time). Maybe this also worked in JUnit 4.

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
}
}

Using #SpyBean with #Qualifier Spring Boot Test

I have 2 DataSources in my app.
So, to get the required JdbcTemplate, i use #Qualifier. But, when i do like below, the test runs... but stays waiting indefinitely, if there is any use of JdbcTemplate in the "Method Under Test".
#Service
#Transactional
public class SampleDatabaseService {
#Autowired
#Qualifier("firstDbJdbcTemplate")
private JdbcTemplate firstDbJdbcTemplate;
#Autowired
#Qualifier("secondDbJdbcTemplate")
private JdbcTemplate secondDbJdbcTemplate;
#Cacheable("status")
public Map<String, Device> readAllValidDeviceStatus() {
Map<String, Device> allDeviceStatuses = new HashMap<>();
//Stops at below line indefinitely if "SpyBean" is used
List<StatusDetail> statusDetails = firstDbJdbcTemplate
.query(SqlQueries.READ_DEVICE_STATUS, BeanPropertyRowMapper.newInstance(StatusDetail.class));
statusDetails
.stream()
.filter(deviceStatus -> deviceStatus.getName() != "Some Invalid Name")
.forEach(deviceStatus -> allDeviceStatuses
.put(deviceStatus.getName(), buildDevice(deviceStatus)));
return allDeviceStatuses;
}
/** More Stuff **/
}
and the Test :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#Transactional
#Rollback
#ActiveProfiles("test")
public class SampleDatabaseServiceTest {
#SpyBean
#Qualifier("firstDbJdbcTemplate")
private JdbcTemplate firstDbJdbcTemplate;
#Autowired
private SampleDatabaseService serviceUnderTest;
#Before
public void populateTables() {
//Insert some Dummy Records in "InMemory HSQL DB" using firstDbJdbcTemplate
}
#Test
public void testReadAllValidDeviceStatus() {
// When
Map<String, Device> allDeviceStatuses = serviceUnderTest.readAllValidDeviceStatus();
// Then
assertThat(allDeviceStatuses).isNotNull().isNotEmpty();
// More checks
}
/* More Tests */
}
But, when i replace the #SpyBean with #Autowired in Test, it works fine.
Why is it so? Any help is greatly appreciated. :)
Use it in below format
#MockBean(name = "firstDbJdbcTemplate")
private JdbcTemplate firstDbJdbcTemplate;

Resources