I am trying to write tests for my rest controller and getting a NullPointerException whenever I attempt to perform actions on the MockMvc instance.
My project is structured as follows:
POJO:
#Entity
public class Pair {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String a;
private String b;
//getters and setters...
}
Rest Controller:
#RestController
public class PairController {
#Autowired
private PairServiceImpl pairService;
#RequestMapping(value = "/pair", method = RequestMethod.POST)
public Pair addPair(String a, String b) {
Pair newPair = new Pair();
newPair.setA(a);
newPair.setB(b);
return pairService.addNewPair(newPair);
}
#RequestMapping(value = "/pair", method = RequestMethod.GET)
public List<Pair> getPairs() {
return pairService.getPairs();
}
}
Service layer:
#Service
public class PairServiceImpl implements PairService {
#Autowired
private PairRepositoryImpl pairRepository;
public Pair addNewPair(Pair newPair) {
return pairRepository.save(newPair);
}
public List<Pair> getPairs() {
return pairRepository.findAll();
}
}
Repository:
public interface PairRepositoryImpl extends JpaRepository<Pair, Long> {
}
And I want to test the PairController API endpoints:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {DemoApplication.class, DatabaseConfig.class})
#AutoConfigureMockMvc
#ContextConfiguration(classes = {PairController.class})
public class PairControllerTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private PairService pairService;
#Test
public void addPairTest() {
Pair testPair = new Pair();
testPair.setA("a");
testPair.setB("b");
ObjectMapper objectMapper = new ObjectMapper();
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/pair").accept(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(testPair))).andReturn();
//The line above throws an exception
int status = mvcResult.getResponse().getStatus();
assertEquals(200, status);
}
}
If I dont add the #ContextConfiguration the tests cannot find my endpoint.
I tried to log the a and b value when the addPair method was invoked, and both values were null. You can also see that I add a custom database config class "DatabaseConfig", which contains a H2 embedded database datasource method such that the tests do not use the production database. The #EnableJpaRepositories annotation is present in this class and it points to the repository shown above.
I have tried to juggle around many different annotations but they all have the same end result: null values in the controller method.
I have also tried to manually constructing the MockMvc instance using #Autowired on the WebApplicationContext and using that to initialize the MockMvc instance in a method with the #Before annotation - but the end result was the same.
I have made a comment below the line that throws an exception, located in the PairControllerTests class.
So if I run the application and test it using Postman and the production database the endpoints work and the data is persisted and retrieved properly. This issue only happens during testing.
Related
I have service method which return mapper to convert entity to DTO when I run the application everything work successfully but when I do unit test the mapper return null.
Also I should mention that, this service is being called by another service "customerDetails" which is under the test.
code snippet, I put comments to describe the problem more :
customerService
public class customerService {
private final CustomerMapper customerMapper;
public Customer customerDetails(int id) {
CustomerDto customer = getById(id) //here is the problem customer is null
// rest of the code
}
public CustomerDto getById(int id) {
Optional<Customer> customer =
this.customerRepository.findCustomerByIdAndIsDeletedFalse(id); //assessment is filled successfully
return this.customerMapper.map(customer.get()); //the mapper her return customerDto and accept customer and it return null in unit test only
}
}
customerServiceTest
public class CustomerServiceTest {
#Mock
private CustomerRepository customerRepository;
#InjectMocks
private CustomerService customerService;
#BeforeEach
public void createMocks() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testCustomerDetails() {
Customer expectedResponse = DummyCustomer.create();
when(customerRepository.findCustomerByIdAndIsDeletedFalse(actualResponse.getId()).thenReturn(Optional.of(expectedResponse));
Customer response = this.CustomerService.customerDetails(expectedResponse.getId());
}
}
In actual code Spring handles injection of your mapper for you - but in unit test you don't have spring context set up. In fact you'd have seen the issue earlier if instead of relying on #InjectMocks you tried to initialize the service manually.
As to solutions - in test code you can get an instance of your mapper using org.mapstruct.factory.Mappers.getMapper() method. Use it and set it in your service under test properly (however you inject your dependencies - via constructor or setter). Or, if you want a "pure" unit test of just one component, mock it.
I'm making test code in spring boot.
But, my test code doesn't save the data using #Before method.
If i request to '/v1/stay/, it return empty array...
Please can you explain what is wrong with my code?
Here is my test code.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class StayControllerTest {
#MockBean
private StayService stayService;
#Autowired
private MockMvc mockMvc;
// givenStay method is the method generating dummy data
#Before
public void before() {
stayService.save(givenStay1());
stayService.save(givenStay2());
stayService.save(givenStay3());
stayService.save(givenStay4());
stayService.save(givenStay5());
}
#Test
#Transactional
void showStayList() throws Exception {
List<StayReq> original = new ArrayList<>();
original.add(givenStay1());
original.add(givenStay2());
original.add(givenStay3());
original.add(givenStay4());
original.add(givenStay5());
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/v1/stay")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andReturn();
System.out.println(result.getResponse());
}
}
And below code blocks are my StayController and StayService
#RestController
#ApiV1
#RequiredArgsConstructor
public class StayController {
private final StayService stayService;
private final ApiService apiService;
#GetMapping("/stay")
public ResponseEntity<Response> stayList() {
return apiService.okResponse(stayService.getList());
}
}
#Service
#RequiredArgsConstructor
public class StayService {
private final StayRepository stayRepository;
private final RoomRepository roomRepository;
public List<StayRes> getList() {
return stayRepository.findAll().stream().map(StayRes::new).collect(Collectors.toList());
}
#Transactional
public void save(StayReq stayReq) {
stayRepository.save(stayReq.toEntity());
}
}
You injected a mock, not a 'real' service. If you want to use a 'real' service - you need to replace #MockBean annotation with #Autowired annotation.
Or alternatively - you can configure mock in the test method to return some predefined data.
In my project I am creating a rest endpoint which is responsible to consume grpc service response.
Now I want to write testcase for the controller class but the Junit test cases returning me null null values .
MyController.java
#RestController
#RequestMapping("/v1")
public class MyController {
#Autowired
private MyConsumerService consumer;
public MyController(MyConsumerService consumer) {
this.consumer=consumer;
}
#GetMapping("/data")
public ResponseEntity<Records> getData(#RequestParam("data") String data) {
Records records = consumer.getGrpcResponse(data);
return new ResponseEntity<>(Records, HttpStatus.OK);
}
}
MyConsumerServiceImpl.java:
public class MyConsumerServiceImpl implements MyConsumerService {
#GrpcClient("user-grpc-service")
private userGrpc.userBlockingStub stub;
#Override
public Records getGrpcResponse(data) {
Records records = new Records();
UserRequest request = UserRequest.newBuilder()
.setUserName(data)
.build();
APIResponse response = stub.userRequest(request);
records.setUserName(response.getUserName());
return records;
}
}
MyControllerTest.java:
#ExtendWith(MockitoExtension.class)
public class MyControllerTest {
private MyConsumerService mockerService;
private MyController controller;
#BeforeEach
void setup(){
mockerService = mock(MyConsumerService.class);
controller = new MyController(mockerService);
}
#Test
public void shouldGet(){
final var data="Hello";
when(mockerService.getGrpcResponse(data)).thenReturn(new Records());
final var responseEntity=controller.getData(data);
assertEquals(responseEntity.getBody(),new Records());
}
}
responseEntity.getBody() is returning null.
Normal flow is working fine but with Junit when I am mocking the client service call, it is returning null.
I am confused why always it is returning null.
Any idea where I am getting wrong.
you have not added when then statement for service.getData(),
and below stubbing have not been called any where
when(mockerService.getGrpcResponse(data)).thenReturn(new Records());
use when then to mock service.getData() like this,
when(mockerService.getData(data)).thenReturn(new Records());
annotate this 'MyControllerTest' class with #WebMvcTest(MyController.class) and the rerun it will work, otherwise its not able to mock actual controller class.
Background
I have a simple SpringBoot application in which I am testing an UPDATE to my Domain Object from a DTO. Naturally - I am using a ModelMapper to convert from DTO->Entity. The issue I am running into is that while the ModelMapper is working perfectly in the live run, its not working during JUNITs. I put a breakpoint in the initBaseModelMapper in my Configuration file during both JUNIT and LIVE runs and the breakpoint hits successfully. But in JUNITS, during the actual mapping - the null values are still being applied to the Domain entity but not during the live run which works perfectly.
Configuration
#Configuration
public class ModelMapperConfiguration {
#Bean(name = "myEntityMapper")
public ModelMapper modelMapper() {
return initBaseModelMapper();
}
public static ModelMapper initBaseModelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setPropertyCondition(Conditions.isNotNull());
modelMapper.getConfiguration().setSkipNullEnabled(true); // Tried without this as well
return modelMapper; // Gets hit during LIVE and JUNITS
}
}
Main Class Method Under Test
public class MyCaseService {
#Autowired
#Qualifier("myEntityMapper")
private ModelMapper modelMapper;
#Override
#Transactional
public #ResponseBody
MyCaseEntity updateMyCase(
#Valid final String myCaseId,
#Valid MyCaseDTO myCase) throws Exception {
MyCaseEntity existingEntity = entityRepository.find(myCaseId);
modelMapper.map(myCase, existingEntity);
return existingEntity;
}
JUNIT
I put a breakpoint the the ModelConfiguration and I can see it getting Initialized exactly like when the code is running live. However, for some reason, the ModelMapper is IGNORING the skipping of null fields unlike when its running live
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes= {ModelMapperConfiguration.class})
public class MyCaseServiceTest {
#InjectMocks
private MyCaseService testSubject;
#Spy
#Qualifier("myEntityMapper")
private ModelMapper modelMapper;
#Before
public void setUp() {
// Initialized `testEntityCase` etc with Id etc
}
#Test
public void testUpdate() throws Exception {
Mockito.when(entityRepository.find(Mockito.any())).thenReturn(testEntityCase);
MyCaseEntity myCase = testSubject.updateMyCase(
"1",
testCaseDTO);
assertEquals(1L, myCase.getId().longValue()); // <- Test Fails with NullPointer. Id becomes null during JUNIT.
}
One way to overcome theses Problems is to autowire the constructur of MyCaseService instesd of the private member
public class MyCaseService {
private ModelMapper modelMapper;
#Autowired
MyCaserService(#Qualifier("myEntityMapper") ModelMapper modelMapper) {
this.modelMapper = modelMapper;
}
#Override
#Transactional
public #ResponseBody
MyCaseEntity updateMyCase(
#Valid final String myCaseId,
#Valid MyCaseDTO myCase) throws Exception {
MyCaseEntity existingEntity = entityRepository.find(myCaseId);
modelMapper.map(myCase, existingEntity);
return existingEntity;
}
}
In the Test you can use the Spy to create the Service
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes= {ModelMapperConfiguration.class})
public class MyCaseServiceTest {
#Spy
#Qualifier("myEntityMapper")
private ModelMapper modelMapper;
private MyCaseService testSubject;
#Before
public void setUp() {
testSubject = new MyCaseService(modelMapper);
// Initialized `testEntityCase` etc with Id etc
}
...
I'm using Hibernate and the JPA with Spring Boot.
I have a DTO which would collect information from a Contact, which, is envisioned to come from a form from the client side which would could potentially have multiple phones sent:
public class ContactDTO {
private BigInteger userId;
private String contactName;
private Map<String, BigInteger> phones;
// getters and setters
}
I'm imagining the data will be sent in a JSON object in this format:
{phones:[{"mobile":"2325552932"}, {"landline":"2235553329"}, ...]
And I have a controller with a POST method designed to handle that:
#PostMapping(path = "/newContact")
public String createNewContact(#ModelAttribute ContactDTO newContact) {
if (newContact.getPhones() !=null) {
// method that persists phone data
}
// .. use a CRUDRepository object to persist the data to MySQL DB
return "savedContact";
}
I guess my questions are twofold:
will my controller be able to automatically map the JSON object in that format?
How would I test that?
I'm using the Spring Boot test, and they look something like this :
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class AdditiveControllerShould {
#Autowired
private AdditiveController additiveController;
#Autowired
private MockMvc mockMvc;
#Test
public void saveAnEntryWhenPOSTOnlyUserIdAndContactName() throws Exception {
mockMvc.perform(post("/newContact")
.param("userId", "12345")
.param("contactName", "John Smith"))
// how to run test for the Map<String, BigInteger> ???
.andExpect(status().isOk())
.andExpect(content().string(containsString("savedContact")));
}
}
Controller :
#PostMapping(value= "/newContact")
public String createNewContact(#RequestBody ContactDTO newContact) {
if (newContact.getPhones() !=null) {
// method that persists phone data
}
// .. use a CRUDRepository object to persist the data to MySQL DB
return "savedContact";
}
Test Class:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class AdditiveControllerShould {
#Autowired
private MockMvc mockMvc;
private static final ObjectMapper mapper=new ObjectMapper();
#Test
public void saveAnEntryWhenPOSTOnlyUserIdAndContactName() throws Exception {
Map<String,String> phones=new HashMap<>();
phones.put("phone1", "12345");
Map<String,Object> input=new HashMap<>();
input.put("userId", "123456");
input.put("contactName", "TEST");
input.put("phones", phones);
mockMvc.perform(post("/newContact")
.content(mapper.writeValueAsString(input))
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(status().isOk());
}
}
output:
CONTACT ContactDTO [userId=123456, contactName=TEST, phones={phone1=12345}]