Problems with Unit_Tests - java

I am trying to write UNIT Test for my controller in MVC - Spring Boot , actually i am new to it . I have added dependency of Unit testing in pom/xml.
Here is my controller :
#GetMapping("/showFormForUpdate/{id}")
public String showFormForUpdate(#PathVariable ( value = "id") long id, Model model) {
// get employee from the service
Employee employee = employeeService.getEmployeeById(id);
// set employee as a model attribute to pre-populate the form
model.addAttribute("employee", employee);
return "update_employee";
}
Here is what i did:
public class ControllerTests {
#Test
void hello(){
EmployeeController controller = new EmployeeController();//Arrange
String response = controller.showFormForUpdate( long id);
}
}
How could i write a good Unit test for this?

Spring offers #WebMvcTest for controller layer slicing test.
(Strictly, it is not unit test. but also not integrated test.)
https://spring.io/guides/gs/testing-web/
for example
#WebMvcTest
public class YourTest() {
#Autowired
private MockMvc mockMvc;
#Test
public void hello() {
this.mockMvc
.perform(get("/showFormForUpdate/111"))
.andDo(print())
.andExpect(status().isOk())
}
}

Related

mapper in unit test is return null

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.

how can i insert advanced data in spring boot test?

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.

Unit test does not work as expected - instead of 404, it returns 200 as status code

I am writing the unit tests for an application which converts an amount of money with a particular currency to EUR. I have in a database some accounts which have: iban, currency and balance.
In order to get all the currencies and their value in EUR, an API call is made in order to get the exchange rate. The method of converting receives the iban of the account and returns the amount of money from the account in EUR.
The app works fine, but I have some issues with the unit tests.
I am trying to write a unit test for the case when the account cannot be found - so when the method from AccountController class - getAccountByIban returns 404. I wrote the test and added as IBAN a random String - but instead of 404, I get 200 as response code and the test fails. If I call the method getAccountByIban with that random IBAN from the test, I get the right response code - 404.
Here is my code:
AccountController:
#RestController
public class AccountController {
#Autowired
RequestBuilder requestBuilder;
#Autowired
AccountService accountService;
#ApiOperation(value = "Get account with given iban ", response = AccountEntity.class)
#RequestMapping(value = "/account/{iban}", method = RequestMethod.GET )
public AccountDTO getAccountByIban(#PathVariable("iban") String iban ) throws AccountNotFoundException {
return Optional
.ofNullable(accountService.getAccountByIban(iban))
.orElseThrow(() -> new AccountNotFoundException("Requested account was not found! [ iban = " + iban + "]"));
}
#ApiOperation(value = "Get account with amount converted ", response = AccountEntity.class)
#RequestMapping(value = "/account/exchange/{iban}", method = RequestMethod.GET )
public AccountDTO getAmountConvertedByIban(#PathVariable("iban") String iban) {
AccountDTO accountDTO = accountService.getAccountByIban(iban);
Double currentBalance = accountDTO.getBalance();
String currentCurrency = accountDTO.getCurrency();
Double actualBalance = accountService.getCurrencyValue(currentCurrency) * currentBalance;
accountDTO.setBalance(actualBalance);
return accountDTO;
}
}
AccountService:
public class AccountService {
#Autowired
AccountRepository accountRepository;
#Autowired
RequestBuilder requestBuilder;
#Autowired
CurrencyDTO currencyDTO;
#Cacheable("exchangeRates")
public Double getCurrencyValue(String currency) {
return currencyDTO.getCurrencyValue(currency);
}
public AccountDTO getAccountByIban(String iban){
AccountDTO accountDTO = null;
AccountEntity accountEntity = accountRepository.findByIban(iban);
if (accountEntity != null ) {
accountDTO = accountEntity.toDTO();
}
return accountDTO;
}
}
Class with UNIT test:
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
#SpringBootTest
public class AccountServiceTest {
#MockBean
AccountController accountController;
#Spy
#InjectMocks
AccountService accountService;
#Mock
AccountRepository accountRepository;
#Autowired
MockMvc mockMvc;
#Before
public void setup() throws Exception {
AccountEntity accountEntity = new AccountEntity();
accountEntity.setIban("RO06PORL4513558777471397");
accountEntity.setBalance(10000D);
accountEntity.setCurrency("RON");
Mockito.when(accountRepository.save(accountEntity)).thenReturn(accountEntity);
}
#Test
public void testAccountNotFound() throws Exception {
mockMvc.perform(get("/account/RO2E2"))
.andExpect(status().isNotFound());
//Assert.assertTrue(accountService.getAccountByIban("RO06PORL4513558777471397") == null);
AccountDTO:
public class AccountDTO {
#ApiModelProperty(notes = "Account ID",name="id",required=true,value="id")
private int id;
#ApiModelProperty(notes = "Account IBAN",name="iban",required=true,value="iban")
private String iban;
#ApiModelProperty(notes = "Account Currency",name="currency",required=true,value="currency")
private String currency;
#ApiModelProperty(notes = "Account Balance",name="balance",required=true,value="balance")
private Double balance;
#ApiModelProperty(notes = "Last Update",name="lastUpdate",required=true,value="lastUpdate")
private Date lastUpdate;
Can anyone help me correct my test class ? I cannot see what I am doing wrong
You are inverting the concept of Controller and Service, and mocking too much, confusing the testing framework.
When you want to test the outcome of a Controller, you should use MockMvc against the real Controller (in a ControllerTest class, not in a ServiceTest class), then mocking the Service used by the Controller to have it return what you want to the Controller.
Something like:
import static org.mockito.Mockito.when;
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
// #SpringBootTest
// Not needed the whole context here, just use the faster way:
#WebMvcTest(AccountControllerTest.class)
public class AccountControllerTest {
#Autowired
MockMvc mockMvc;
#MockBean
AccountService accountService;
#Test
public void testAccountFound() throws Exception {
when(accountService.getAccountByIban("123"))
.thenReturn(new AccountDTO(...));
mockMvc.perform(get("/account/123"))
.andExpect(status().isOk());
}
#Test
public void testAccountNotFound() throws Exception {
when(accountService.getAccountByIban("123"))
.thenReturn(null);
mockMvc.perform(get("/account/123"))
.andExpect(status().isNotFound());
}
}
Then, when you need to test the Service in a ServiceTest class, you won't use MockMvc, because you want to test the communication between Service and Repository, not between Controller and Service. Always follow the flow.
There, you will mock the repository, and test that Service outputs the proper results with the different (mocked) repository results.
That said, when the return type of a method is optional, you should use Optional as return type, not null and then using Optional externally, hence:
public Optional<AccountDTO> getAccountByIban(String iban){
AccountEntity accountEntity = accountRepository.findByIban(iban);
if (accountEntity != null ) {
return Optional.of(accountEntity.toDTO());
}
return Optional.empty();
}
And
#ApiOperation(value = "Get account with given iban ", response = AccountEntity.class)
#RequestMapping(value = "/account/{iban}", method = RequestMethod.GET )
public AccountDTO getAccountByIban(#PathVariable("iban") String iban ) throws AccountNotFoundException {
return accountService.getAccountByIban(iban)
.orElseThrow(() -> new AccountNotFoundException("Requested account was not found! [ iban = " + iban + "]"));
}
And
when(accountService.getAccountByIban("123"))
.thenReturn(Optional.of(new AccountDTO(...)));
when(accountService.getAccountByIban("123"))
.thenReturn(Optional.empty());
Also make sure that AccountNotFoundException is annotated with
#ResponseStatus(HttpStatus.NOT_FOUND)
You are getting strange results, because you are trying to test your service, using mockMvc which is helpful for unit testing controllers or doing some integration tests. It seems that Mocking your controller bean results in returning always 200 status.
What I see is you are mixing unit test with integration test, because you are using #SpringBootTest along with repository mocking.
I recommend to convert your test to unit and not throw an exception in your controller. Your accountService may return Optional<AccountDto> and your controller then may return ResponseEntity<AccountDto>. Then code in your controller method may look like:
return dto.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
and you're in your test setup you may mock your service with empty optional.
Moreover, if you want to use integration tests, then you can use an embedded H2 database for that, and insert some data to your database before the test.

Spring boot REST: cannot test validation of #RequestParam

I want to test controller of Spring boot API with class structure as below:
Controller:
#RestController
#RequestMapping("/member-management")
#Validated
public class MemberManagementController {
private final MemberManagementService memberManagementService;
public MemberManagementController(MemberManagementService memberManagementService) {
this.memberManagementService = memberManagementService;
}
#GetMapping(value = "/view-member")
public ResponseEntity<?> viewMember(
#NotBlank(message = "username must not be blank!!")
#Size(max = 20, message = "maximum size of username id is 20!!")
#RequestParam("username") String username) {
...
}
...
Controller advice:
#ControllerAdvice
public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Object> handleConstaintViolatoinException(final ConstraintViolationException ex) {
List<String> details = new ArrayList<>();
Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
for (ConstraintViolation<?> violation : violations) {
details.add(violation.getMessage());
}
ApiErrorResUtil error = new ApiErrorResUtil(String.valueOf(HttpStatus.BAD_REQUEST.value()),
"Request param error", details);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
...
}
Unit test:
#RunWith(MockitoJUnitRunner.class)
public class MemberManagementControllerTest {
#InjectMocks
private MemberManagementController memberManagementController;
#Mock
private MemberManagementService memberManagementService;
private MockMvc mockMvc;
#Before // Execute before each test method
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(memberManagementController)
.setControllerAdvice(new CustomRestExceptionHandler()) // add ControllerAdvice to controller test
.build();
}
#Test
public void viewMember_usernameSizeExceedsMaximumLimit() throws Exception {
// Value from client
String username = "a12345678901234567890"; // 21 characters
MemberResDtoDataDummy memberResDtoDataDummy = new MemberResDtoDataDummy();
when(memberManagementService.viewMember(username)).thenReturn(memberResDtoDataDummy.getMember1());
mockMvc.perform(get("/member-management/view-member").param("username", username))
.andExpect(status().isBadRequest()).andReturn();
}
Problem:
java.lang.AssertionError: Status expected:<400> but was:<200>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
...
Could anybody help me to resolve this proplem, why expected status is 200 instead of 400, other tests of POST, PUT, PATCH, DELETE request method with invalid inputted param are still working fine :(
When you want to test your UI layer without the cost of starting a server, you have to define this test as a spring-boot one and autowired the MockMvc.
#SpringBootTest
#AutoConfigureMockMvc
This class-annotations will load all the applicationContext without server.
If you just want to load your web layer, put just this annotation on your test class.
#WebMvcTest
With this annotation Spring Boot instantiates only the web layer rather than the whole context.
In both case you have to autowired the MockMvc type.

SpringBoot Nullpointer exception during api testing

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.

Categories

Resources