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