Mock Spring Data Rest Repository - spring

I faced with problem while testing data rest repositories. I call rest resource and check if it gets me proper json. But for pre-population data I don't want to use in memory db, so I mocked repository method invocation.
#MockBean
private CommentRepository commentRepository;
and did this
given(commentRepository.findOne(1L)).willReturn(comment);
And now, while calling "/comments/1" I get 404 error, so data rest didn't expose my repository. The main question is "How can we mock repository method for getting data from database?"
My test class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CommentTest
{
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private CommentRepository commentRepository;
#Before
public void setup()
{
Comment comment = new Comment();
comment.setText("description");
comment.setCommentId(1L);
given(commentRepository.findOne(1L)).willReturn(comment);
}
#Test
public void shouldCheckCommentGetResource()
{
ParameterizedTypeReference<Resource<Comment>> responseType = new ParameterizedTypeReference<Resource<Comment>>() {};
ResponseEntity<Resource<Comment>> responseEntity =
restTemplate.exchange("/comments/1", HttpMethod.GET, null, responseType, Collections
.emptyMap());
Comment actualResult = responseEntity.getBody().getContent();
assertEquals("description", actualResult.getText());
// more assertions
}
}
As I understand, using MockBean annotation I replace current repository bean and it will not be exposed by data rest, Do we have any way to pre-populate data into db or mock invocation of repository method?

I do not think this is possible. Spring data registers the repository beans using a FactoryBean - in the spring-data-rest case this is EntityRepositoryFactoryBean. So you cannot just override these beans with a mock.
For an interesting read on why mocking spring data repositories is not useful see this answer https://stackoverflow.com/a/23442457/5371736
In the same thread there is a reference to a project introducing mock support for spring-data repositories - https://stackoverflow.com/a/28643025/5371736

There is quick and dirty way to mock Spring Data Rest repositories with mockito, just in case somebody have no other options, but try to avoid this unless absolutely necessary
class MockRestRepositoryUtil {
public static <T> T mockRepository(Class<T> repositoryClass,
T springRepository) throws Exception {
Object springRepositoryImpl = AopTestUtils.getTargetObject(springRepository);
T mockRepository = mock(repositoryClass, delegatesTo(springRepositoryImpl));
Object springProxyHandler = Proxy.getInvocationHandler(springRepository);
ProxyFactory proxyFactory = extractProxyFactory(springProxyHandler);
proxyFactory.setTarget(mockRepository);
removeSpringDataAspects(proxyFactory);
return mockRepository;
}
private static ProxyFactory extractProxyFactory(Object springProxyHandler) throws Exception {
Field advisedField = springProxyHandler.getClass().getDeclaredField("advised");
advisedField.setAccessible(true);
return (ProxyFactory) advisedField.get(springProxyHandler);
}
private static void removeSpringDataAspects(ProxyFactory proxyFactory) {
Advisor[] advisors = proxyFactory.getAdvisors();
Arrays.stream(advisors)
.filter(advisor -> advisor.getAdvice().getClass().getPackage().getName()
.contains("org.springframework.data"))
.collect(toImmutableList())
.forEach(proxyFactory::removeAdvisor);
}
}

Related

mock rest template for unit test

I want to mock a RestTemplate in Spring Boot, where I'm making a REST call within a method. To test the controller of the microservice I'm creating,
I want to test methods inside the controller of my micro service.
For example:
#GetMapping(value = "/getMasterDataView", produces = { MediaType.APPLICATION_JSON_VALUE })
#CrossOrigin(origins = { "http://192.1**********" }, maxAge = 3000)
public ResponseEntity<MasterDataViewDTO> getMasterDataView() throws IOException {
final String uri = "http://localhost:8089/*********";
RestTemplate restTemplate = new RestTemplate();
MasterDataViewDTO masterDataViewDTO = restTemplate.getForObject(uri, MasterDataViewDTO.class);
return new ResponseEntity<>(masterDataViewDTO, HttpStatus.OK);
}
how to I test this using mocking?
This is what I have so far:
#Test
public void testgetMasterDataView() throws IOException {
MasterDataViewDTO masterDataViewDTO= mock(MasterDataViewDTO.class);
//String uri = "http://localhost:8089/*********";
Mockito.when(restTemplate.getForObject(Mockito.anyString(),ArgumentMatchers.any(Class.class))).thenReturn(masterDataViewDTO);
assertEquals("OK",inquiryController.getMasterDataView().getStatusCode());
}
I am getting an error when I'm running the mock, the method getMasterDataView() is getting called and the REST call within it is also getting called and is throwing an error. How can I write my test so that the REST endpoint is not called? If it's possible, I'd like to do this with Mockito.
Before you start writing a test, you should change your code a bit. First of all, it would be a lot easier if you extracted that RestTemplate, and created a separate bean for it which you would inject within your controller.
To do that, add something like this within a #Configuration class or within your main class:
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
Additionally, you have to remove the new RestTemplate() from your controller, and autowire it in stead, for example:
#Autowired
private RestTemplate restTemplate;
Now that you've done that, it's going to be a lot easier to inject a mock RestTemplate within your tests.
For your testing, you have two options:
Either mock RestTemplate and all the methods you are trying to access, using a mocking framework (eg. Mockito)
Or you can use MockRestServiceServer, which allows you to write tests that verify if the URLs are properly called, the request matches, and so on.
Testing with Mockito
To mock your RestTemplate with Mockito, you have to make sure that you add the following annotation to your tests:
#RunWith(MockitoJUnitRunner.class)
After that, you can do this:
#InjectMocks
private MyController controller;
#Mock
private RestTemplate restTemplate;
And now you can adjust your tests like this:
#Test
public void testgetMasterDataView() throws IOException {
MasterDataViewDTO dto = new MasterDataViewDTO();
when(restTemplate.getForObject("http://localhost:8089/*********", MasterDataViewDTO.class)).thenReturn(dto);
ResponseEntity<MasterDataViewDTO> response = controller.getMasterDataView();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isEqualTo(dto);
}
You could mock the DTO as you did within your test, but you don't have to, and I don't think there's any benefit from doing so. What you do have to mock is the restTemplate.getForObject(..) call.
Testing with MockRestServiceServer
Another approach is to use MockRestServiceServer. To do that, you have to use the following annotations for your test:
#RunWith(SpringRunner.class)
#RestClientTest
And then you'll have to autowire your controller and MockRestServiceServer, for example:
#Autowired
private MyController controller;
#Autowired
private MockRestServiceServer server;
And now you can write tests like this:
#Test
public void testgetMasterDataView() throws IOException {
server
.expect(once(), requestTo("http://localhost:8089/*********"))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(new ClassPathResource("my-mocked-result.json"), MediaType.APPLICATION_JSON));
ResponseEntity<MasterDataViewDTO> response = controller.getMasterDataView();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
// TODO: Write assertions to see if the DTO matches the JSON structure
}
In addition to testing that your actual REST call matches, this also allows you to test if your JSON-to-DTO works as well.
You can achieve this by using #RestClientTest and MockRestServiceServer. An example provided in their documentation:
#RunWith(SpringRunner.class)
#RestClientTest(RemoteVehicleDetailsService.class)
public class ExampleRestClientTest {
#Autowired
private RemoteVehicleDetailsService service;
#Autowired
private MockRestServiceServer server;
#Test
public void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails()
throws Exception {
this.server.expect(requestTo("/greet/details"))
.andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
String greeting = this.service.callRestService();
assertThat(greeting).isEqualTo("hello");
}
}
create bean instead of using new RestTemplate() in your services.
use Profile("!test") for your bean that use only for non test profile.
create test class like this:
#SpringBootTest(properties = "spring.profiles.active:test")
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#Log4j2
#Transactional
public class QabzinoMockTest {
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private QabzinoLogRepository qabzinoLogRepository;
private MockMvc mockMvc;
private final WebServiceStatus successStatus = new WebServiceStatus();
#Mock
private RestTemplate restTemplate;
private final Gson mapper = new Gson();
#TestConfiguration
static class Config {
#Bean
public RestTemplate rest() {
return Mockito.mock(RestTemplate.class);
}
}
#Before
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
this.restTemplate = (RestTemplate) this.webApplicationContext.getBean("rest");
LoginOutput responseModel = new LoginOutput();
responseModel.setStatus("200");
Mockito.when(
restTemplate.postForEntity(
Mockito.eq(LOGIN_URL),
Mockito.isA(HttpEntity.class),
Mockito.eq(String.class)
)
).thenReturn(ResponseEntity.ok().body(mapper.toJson(responseModel)));
}
}
in this sample we create bean with rest name in static class Config and mock it in setup method Before all tests.
and preferred test config maybe useful for you application-test.properties:
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE;IGNORECASE=TRUE;
spring.datasource.username=sa
spring.datasource.password=sa
and finally when we see restTemplate in our code, mock bean return our responseModel instead of real service call :)

What is sense of #Mock annotation?

I have a question about bean creation in testing of controllers. For example, there is a such test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {MainApplicationConfiguration.class, JPAConfig.class})
#WebAppConfiguration
public class TestMainController {
private MockMvc mockMvc;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(mainController).build();
}
#InjectMocks
private MainController mainController;
#Mock
private EntryService entryService;
#Autowired
DBEntryRepository repository;
#Test
public void testEntryGet() throws Exception {
List<DBEntry> response_data = new ArrayList<>();
response_data.add(new DBEntry(1, 1, "STR", "DATE"));
Mockito.when(entryService.findAllEntries())
.thenReturn(response_data);
MvcResult result = mockMvc.perform(get("/VT/entry/"))
.andExpect(status().isOk()).andReturn();
verify(entryService, times(1)).findAllEntries();
verifyNoMoreInteractions(entryService);
}
}
and a controller method mapped on
/VT/entry/
#RequestMapping(value = "/entry/", method = RequestMethod.POST)
public ResponseEntity<Void> createEntry(#RequestBody DBEntry entry, UriComponentsBuilder ucBuilder) {
System.out.println("Creating entry " + entry.getNum());
try {
entryService.saveEntry(entry);
entryService.refreshEntryService();
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<Void>(HttpStatus.BAD_REQUEST);
}
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ucBuilder.path("/entry/{id}").buildAndExpand(entry.getId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
EntryService is annotated with #Service annotation and MainApplicationConfiguration.class is a configuration with #EnableWebMvc and scan project for this EntryService.
by that I want to show that this controller really uses this EntryService in a real application and all are coupled by MainApplicationConfiguration.class.
Question is: Why entryService with #Mock annotation ended up in my controller code in the scope of my test execution? Shouldn't it be only for that instance only and inside of controller should be instantiated another bean(EntryService), why this annotation has mocked all occurrences of that bean (in the test scope)? I was thinking, that I should write whole other context web-context instead of MainApplicationConfiguration.class to mock it inside and substitute current definition. I am absolutely confused why this simple annotation have made such thing.
And if somebody can understand this magic, please say what is difference between #InjectMock and #Mock?
thanks for your attention! and sorry if my question is quite stupid. I am very new, it works, but I have not got magic yet.
In the documentation for #InjectMocks:
Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection in order
So since EntryService is a dependency of your controller, #InjectMocks will try to find a mock object of EntryService in your test class and inject it into mainController.
Note that only one of constructor injection, setter injection, or property injection will occur.
#Mock marks the fields as mock objects.
#InjectMocks injects mock objects to the marked fields, but the marked fields are not mocks.

Cannot inject #Service in Unit Test in SpringBoot project

i have a #Service that I am trying to mock in an Unit Test but i get a null value so far. In the application class I specify what are the scanBasePackages. Do I have to do this in a different way? Thanks.
This is my service class that implements an interface:
#Service
public class DeviceService implements DeviceServiceDao {
private List<Device> devices;
#Override
public List<Device> getDevices(long homeId) {
return devices;
}
}
This is my unit test.
public class SmartHomeControllerTest {
private RestTemplate restTemplate = new RestTemplate();
private static final String BASE_URL = “..”;
#Mock
private DeviceService deviceService;
#Test
public void getHomeRegisteredDevices() throws Exception {
Device activeDevice = new DeviceBuilder()
.getActiveDevice(true)
.getName("Alexa")
.getDeviceId(1)
.getHomeId(1)
.build();
Device inativeDevice = new DeviceBuilder()
.getInactiveDevice(false)
.getName("Heater")
.getDeviceId(2)
.getHomeId(1)
.build();
UriComponentsBuilder builder = UriComponentsBuilder
.fromUriString(BASE_URL + "/1/devices");
List response = restTemplate.getForObject(builder.toUriString(), List.class);
verify(deviceService, times(1)).getDevices(1);
verifyNoMoreInteractions(deviceService);
}
You have to use a Spring test runner if you want to load and use a Spring context during tests execution.
You don't specify any runner, so it uses by default the runner of your test API. Here is probably JUnit or TestNG (the runner using depends on the #Test annotation specified).
Besides, according to the logic of your test, you want to invoke the "real"
REST service :
List response = restTemplate.getForObject(builder.toUriString(),
List.class);
To achieve it, you should load the Spring context and load the Spring Boot container by annotating the test with #SpringBootTest.
If you use a Spring Boot context, to mock the dependency in the Spring context, you must not use #Mock from Mockito but #MockBean from Spring Boot.
To understand the difference between the two, you may refer to this question.
Note that if you are using the #SpringBootTest annotation, a TestRestTemplate is automatically available and can be autowired into your test.
But beware, this is fault tolerant. It may be suitable or not according to your tests.
So your code could look like :
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SmartHomeControllerTest {
private static final String BASE_URL = “..”;
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private DeviceService deviceService;
#Test
public void getHomeRegisteredDevices() throws Exception {
...
}
As a side note, avoid using raw type as List but favor generic type.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = NotificationApplication.class)
public class EmailClientImplTest {
...
}
And also add the needed properties/configs in
/src/test/resources/application.yml
Good luck!
I figured it out, I am using Mockito and used that to annotate my test class. This allowed me to get a mock of the service class that i am trying to use.
#RunWith(MockitoJUnitRunner.class)
public class SmartHomeControllerTest {..
#Mock
private DeviceService deviceService;
}
Try with #InjectMock instead of #Mock
You should run your test with spring boot runner

GridFsTemplate NullPointerException in Service Unit Test Class (Tech Stack: Spring Data / Spring Boot / Micro Service / Mongodb )

I am developing a spring boot app.
The service method uploads a PDF into a mongodb repo using GridFsTemplate which is autowired in the service.
This file upload service method works as expected via postman rest client.
But, When I tried running a unit test; calling the same service method, the SpringData GridFsTemplate is not initialised (In MongoDB, you can use GridFS to store binary files). This results in the org.springframework.data.mongodb.gridfs.GridFsTemplate.store(...) throwing a NullPointerException.
Please, can you help, I have been stuck in this for a days.
Below is my service implementation:
#Service
public final class UploadServiceImpl implements UploadService {
#Autowired
private SequenceRepository sequenceDao;
#Autowired (required = true)
private GridFsTemplate gridFsTemplate;
#Override
public Long uploadFile(Invoice uploadedInvoice) {
ByteArrayInputStream byteArrayInputStream = null;
if (checkContentType(invoiceInfo.getContentType())) {
invoiceInfo.setPaymentID(sequenceDao.getNextSequenceId(INVOICE_UPLOAD_SEQ_KEY));
byteArrayInputStream = new ByteArrayInputStream(uploadedInvoice.getFileContent());
//Error thrown is java.lang.NullPointerException: null, where gridFsTemplate is null and basically autowire does not work when test is run.
GridFSFile gridFSUploadedFile= gridFsTemplate.store(byteArrayInputStream, invoiceInfo.getFileName(), invoiceInfo.getContentType(), invoiceInfo);
return 1l;
} else {
return 2l;
}
}
### Below is my Unit Test class for the service
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration
public class UploadServiceTest {
#Mock
private SequenceRepository sequenceRepositoryMock;
#Autowired
private GridFsTemplate gridFsTemplateMock;
#Mock
private Invoice invoiceMock;
#InjectMocks
private static UploadService uploadService = new UploadServiceImpl();
DBObject fileMetaData = null;
DB db = null;
Jongo jongo = null;
#Before
public void setUp() throws Exception {
db = new Fongo("Test").getDB("Database");
jongo = new Jongo(db);
}
#Test
public void testUploadFile() {
//test 1
Long mockPaymentNo = new Long(1);
Mockito.when(sequenceRepositoryMock.getNextSequenceId(INVOICE_SEQUENCE)).thenReturn(mockPaymentNo);
assertEquals(mockPaymentNo, (Long) sequenceRepositoryMock.getNextSequenceId(INVOICE_SEQUENCE));
//test 2
Invoice dummyInvoice = getDummyInvoice();
InvoiceInfo dummyInvoiceInfo = dummyInvoice.getInvoiceInfo();
MongoCollection invoicesCollection = jongo.getCollection("invoices");
assertNotNull(invoicesCollection.save(dummyInvoiceInfo));
assertEquals(1, invoicesCollection.save(dummyInvoiceInfo).getN());
System.out.println("TEST 2 >>>>>>>>>>>>>>>>>> "+ uploadService);
//test 3 : The following line is the cause of the exception, the service method is called but the GridFsTemplate is not initialized when the test is run. But it works when the endpoint is invoked via postman
uploadService.uploadFile(dummyInvoice);
System.out.println("TEST 3 >>>>>>>>>>>>>>>>>> ");
}
}
The problem is because you use #InjectMocks to autowire your UploadService.
And UploadService autowire two other beans SequenceRepository and GridFsTemplate.
If you’re doing TDD or not (and we are able to change the test first)
– clients of this code don’t know about an additional dependency,
because it’s completely hidden
The Javadoc states:
Mockito will try to inject mocks only either by constructor injection,
setter injection, or property injection in order and as described
below. If any of the following strategy fail, then Mockito won’t
report failure; i.e. you will have to provide dependencies yourself.
The solution is to use UploadServiceImpl constructor to autowire beans:
#Service
public final class UploadServiceImpl implements UploadService {
private final SequenceRepository sequenceDao;
private final GridFsTemplate gridFsTemplate;
private final PlannerClient plannerClient;
#Autowired
public PlannerServiceImpl(PlannerClient plannerClient, GridFsTemplate gridFsTemplate, SequenceRepository sequenceDao) {
this.plannerClient = plannerClient;
}
...
}
When there are more dependencies needed, they’re clearly in sight because they are initialized in constructor
More detailed:
https://tedvinke.wordpress.com/2014/02/13/mockito-why-you-should-not-use-injectmocks-annotation-to-autowire-fields/

In my JUnit test, how do I verify a Spring RedirectView?

I'm using Spring 3.2.11.RELEASE and JUnit 4.11. In a particular Spring controller, I have a method that ends thusly ...
return new ModelAndView(new RedirectView(redirectUri, true));
In my JUnit test, how do I verify return from a submission to my controller in which this RedirectView is returned? I used to use org.springframework.test.web.AbstractModelAndViewTests.assertViewName, but that only returns "null", even when a non-empty ModelAndView object is returned. Here is how I'm constructing my JUnit test ...
request.setRequestURI(“/mypage/launch");
request.setMethod("POST");
…
final Object handler = handlerMapping.getHandler(request).getHandler();
final ModelAndView mav = handlerAdapter.handle(request, response, handler);
assertViewName(mav, "redirect:/landing");
Any help on how to verify that a RedirectView comes back with the proper value is appreciatd,
As Koiter said, consider moving to spring-test a and MockMvc
It providers some methods to test controllers and requests/reponses in a declarative way
you will need a #Autowired WebApplicationContext wac;
and on your #Before method setup this to use the #WebAppConfiguration of the class.
You'll end up with something
#ContextConfiguration("youconfighere.xml")
//or (classes = {YourClassConfig.class}
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
public class MyControllerTests {
#Autowired WebApplicationContext wac
private MockMvc mockMvc;
#Before
public void setup() {
//setup the mock to use the web context
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
}
Then you just need to use the MockMvcResultMatchers to assert things
#Test
public void testMyRedirect() throws Exception {
mockMvc.perform(post("you/url/")
.andExpect(status().isOk())
.andExpect(redirectUrl("you/redirect")
}
Note: post(), status() isOk() redirectUrl() are statics imports from MockMvcResultMatchers
See more what you can match here
Considering change your tool to MockMvc.
First you should create your MockMvc based on your controller.
private MockMvc mockController;
mockController =
MockMvcBuilders.standaloneSetup(loginController).setCustomArgumentResolvers(
new ServletWebArgumentResolverAdapter(new PageableArgumentResolver())).build();
After you create that object build the request with the request information. Part of this is the assert options that are contained in the API.
mockController.perform(MockMvcRequestBuilders.get(LoginControllerTest.LOGIN_CONTROLLER_URL + "?logout=true").
principal(SessionProvider.getPrincipal("GonLu004")))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.view().name("jsp/login"))
.andExpect(MockMvcResultMatchers.model().attribute("logOutMessage", logoutMessage));
The MockMvcResultMatchers contains a method for reviewing redirect information.
MockMvc from spring is a good choice to apply your unit testing on the controller layer.

Resources