The following is my Rest Controller:
#RestController
public class MyController {
#Autowired
private MyService myService;
private boolean deprecatedAPICall = false;
#PostMapping(value = "/deprecatedEndPoint", produces = "application/json")
public ResponseEntity<> deprecatedEndPoint(#RequestBody DeprecatedRequestBody deprecatedRequestBody){
deprecatedAPICall = true;
NewRequestBody newRequestBody = preProcess(deprecatedRequestBody);
result = newEndPoint(newRequestBody);
postProcess(result);
return new ResponseEntity<>(result)
}
#PostMapping(value = "/newEndPoint", produces = "application/json")
public ResponseEntity<> newEndPoint(#RequestBody NewRequestBody newRequestBody){
...
if(deprecatedAPICall) {
deprecatedAPICall = false;
...
}
result = myService.process(newRequestBody);
return new ResponseEntity<>(result)
}
Since the Rest Controller is having a singleton scope, is it a good practise to maintain a mutable field (deprecatedAPICall) in this class? What can possibly go wrong in this class in production?
Related
I want to write a unit test for a controller that uses a FormFactory object
My controller:
public class LoginController extends ApiController {
private final FormFactory formFactory;
#Inject
public LoginController(FormFactory formFactory) {
this.formFactory = formFactory;
}
public Result login(Http.Request request) {
DynamicForm form = formFactory.form().bindFromRequest(request);
String username = form.get("username");
String password = form.get("password");
doSomething();
return result;
}
}
In my test class I'll have to mock the external dependency FormFactory and to stub .form() and .bindFromRequest(request) as well, but I don't know how.
My test class is:
public class LoginControllerTest {
#InjectMocks
private LoginController controller;
#Mock
private FormFactory formFactory;
#Before
public void setup() {
MockitoAnnotations.openMocks(this);
}
#Test
public void login_ok() {
Map<String, String> formData = new HashMap<>();
formData.put("username", "user");
formData.put("password", "pass");
Http.RequestBuilder fakeRequest = new Http.RequestBuilder().method(Helpers.POST).bodyForm(formData);
when(formFactory.form()).thenReturn(new DynamicForm(???));
Result result = controller.adminLogin(fakeRequest.build());
}
}
If I test my class I got a NPE because .form() is null in LoginController class.
How Can I solve this issue?
What parameters I have to pass in new DynamicForm(???)?
I am writing JUnit tests for a controller class. I have tried several methods, but the when.thenReturn() is getting bypassed every time. Following is the sample code:
Controller class:
#RestController
public class FundController {
#Autowired
private FundDAO msDAO = new FundDAO();
private FundUtil msUtil = new FundUtil();
#PostMapping(value = "/v1/fund/search", produces = { MediaType.APPLICATION_JSON_VALUE })
public FundSearchResponse fundNameSearch(
#ApiParam(name = "fundName", value = "fund names in form of JSON", required = true) #RequestBody(required = true) fundName fundNameRequest,
#ApiParam(name = "limit", value = "Number of Records per page", required = false, defaultValue = "10") #RequestParam(value = "limit", required = false, defaultValue = "10") Integer limit) {
FundSearchResponse fundSearchResponse = new FundSearchResponse();
if (!msUtil.validatefundSearchRequest(fundNameRequest, limit)) {
String validationMsg = msUtil.getValidationMsg();
fundSearchResponse.setResponse(
msUtil.buildServiceResponse(Constants.CODE_400_BAD_REQUEST, Constants.TYPE_400_BAD_REQUEST,
validationMsg.isEmpty() ? Constants.DESC_400_BAD_REQUEST : validationMsg));
fundSearchResponse.setfunds(null);
fundSearchResponse.setTotalRecords(0);
}
else {
try {
fundSearchResponse = msDAO.fundNameSearch(fundNameRequest.getfundName(), limit);
if (fundSearchResponse.getfunds() != null) {
fundSearchResponse.setTotalRecords(fundSearchResponse.getfunds().size());
fundSearchResponse.setResponse(msUtil.buildServiceResponse(Constants.CODE_200_SUCCESS));
} else {
fundSearchResponse.setTotalRecords(0);
fundSearchResponse.setResponse(msUtil.buildServiceResponse(Constants.CODE_200_SUCCESS,
Constants.TYPE_200_SUCCESS, Constants.DESC_404_NOT_FOUND));
}
} catch (ApiException e) {
fundSearchResponse.setResponse(msUtil.buildServiceResponse(e.code, e.type, e.getMessage()));
fundSearchResponse.setTotalRecords(0);
}
}
return fundSearchResponse;
}
JUnit test class:
#WebMvcTest(controllers = FundController.class)
#ActiveProfiles("test")
public class FundTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private FundDAO msDAO;
private FundUtil msUtil;
private fundName fundName;
#Before
public void setUp() {
fundName = MockData.getfundName();
msUtil = new FundsOrchestratorUtil();
msDAO = new FundsOrchestratorDAO();
}
#Test
public void shouldFetchAllUsers() throws Exception {
fundsSearchResponse fundSearchResponse = MockData.getfundsSearchResponse();
when(msUtil.validatefundSearchRequest(fundName, 5)).thenReturn(true); // Problem : getting bypassed to Util class
//given(msUtil.validatefundSearchRequest(Mockito.any(fundName.class), Mockito.anyInt())).willReturn(true);
given(msDAO.fundNameSearch(Mockito.anyString(), Mockito.anyInt())).willReturn(fundSearchResponse);
this.mockMvc.perform(post("/v1/funds/search"))
.andExpect(status().isOk());
}
}
I followed this web site JUnit and Mockito, as my usual way of JUnit (#RunWith(SpringJUnit4ClassRunner.class)) were not working. Though both of these are almost same, the problem still persists. As the class call restriction using when().thenReturn() are not working. I am not good at JUnit, so I might be missing something. Please let me know how to get this done. As the dependent class is looking for the data in object which in this case is passed as Mockito.any(Classname.class). When passed with the object with data, its giving error
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
You have to create a mock first :
FundUtil fundUtilMock = org.mockito.Mockito.mock(FundUtil.class);
Then you can call :
when(fundUtilMock.validatefundSearchRequest(fundName, 5)).thenReturn(true);
I have the following REST API controller class.
The endpoint retrieves a paged list of customers.
#RestController
public class CustomerController {
#Autowired
private CustomerRepository customerRepository;
public void setCustomerRepositoryMock(CustomerRepository mockedCustomerRepository) {
this.customerRepository = mockedCustomerRepository;
}
#GetMapping(value="/customers", produces = "application/json")
public ResponseEntity<Page<Customer>> customersList(
#RequestParam(value="pageNumber") int pageNumber,
#RequestParam(value="pageSize") int pageSize){
Pageable customersPageable = PageRequest.of(pageNumber, pageSize);
Page<Customer> customersList = customerRepository.findAll(customersPageable);
return new ResponseEntity<Page<Customer>>(customersList, HttpStatus.OK);
}
}
Now I want to create a mocked unit test for that method.
This is what I have.
public class CustomerControllerTest {
private CustomerRepository mockedCustomerRepository;
private CustomerController customerController;
private Customer customer;
private Page<Customer> customersList;
Pageable customersPageable;
#BeforeEach
void setup() {
customer = new Customer();
customer.setName("Pete");
customer.setAge(35);
customer.setEmail("pete#test.com");
List<Customer> customersListTest = new ArrayList<Customer>();
customersListTest.add(customer);
customersList = new PageImpl<>(customersListTest);
mockedCustomerRepository = mock(CustomerRepository.class);
customerController = new CustomerController();
customerController.setCustomerRepositoryMock(mockedCustomerRepository);
}
#Test
void testListCustomers() {
when(mockedCustomerRepository.findAll(customersPageable)).thenReturn(customersList);
ResponseEntity<Page<Customer>> respPageCustomers = customerController.customersList(0, 3);
assertTrue(respPageCustomers.getBody() != null);
}
}
The problem is that when the following line is executed (in the API method), CustomerList is null.
Page<Customer> customersList = customerRepository.findAll(customersPageable);
But it should have content, because the content was added in the setup method of the test class and then in the following line of the test method.
when(mockedCustomerRepository.findAll(customersPageable)).thenReturn(customersList);
Replace
when(mockedCustomerRepository.findAll(customersPageable)).thenReturn(customersList);
with
when(mockedCustomerRepository.findAll(any(Pageable.class))).thenReturn(customersList);
What you currently have - is that mocked repository will return result only when it receives exact customersPageable (which is null). Using any() will return expected result if any object of mentioned class will be passed as parameter
I've made rest controller, that calls #service class:
#Service
public class UnitServiceImpl extends HttpRequestServiceImpl implements UnitService {
#Override
public Unit addUnit(String unitName) {
final Unit unit = new Unit();
unit.setUnitName(unitName);
return unitRepository.save(unit);
}
#Override
public Unit getUnit(int id) {
final Unit unit = unitRepository.findById(id);
if (unit == null) {
throw new EntityNotFoundException("Unit is not found");
}
return unit;
}
#Override
public Iterable<Unit> getAllUnits() {
return unitRepository.findAll();
}
}
EnityNotFoundException is handled by ExceptionHandlingController:
#RestController
#ControllerAdvice
public class ExceptionHandlingController extends ResponseEntityExceptionHandler {
#ExceptionHandler({RuntimeException.class})
public final ResponseEntity<ErrorDetails> handleRuntimeException(RuntimeException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
if (ex.getClass() == EntityNotFoundException.class) {
httpStatus = HttpStatus.NOT_FOUND;
}
return new ResponseEntity<>(errorDetails, httpStatus);
}
}
Unit controller just calls the getUnit:
#RestController
public class UnitController {
private final UnitService managementService;
#PostMapping(value = "/unit", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Unit> addUnit(HttpServletRequest request) throws FieldsIsAbsentException {
final String unitName = managementService.getParameter(request, "unit_name");
final Unit unit = managementService.addUnit(unitName);
return new ResponseEntity<>(unit, HttpStatus.CREATED);
}
public UnitController(UnitService managementService) {
this.managementService = managementService;
}
#GetMapping(value = "/unit", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Iterable<Unit>> getAllUnits() {
final Iterable<Unit> allUnits = managementService.getAllUnits();
return new ResponseEntity<>(allUnits, HttpStatus.OK);
}
#GetMapping(value = "/unit/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Unit> getUnitById(#PathVariable("id") int id) {
final Unit unit = managementService.getUnit(id);
return new ResponseEntity<>(unit, HttpStatus.CREATED);
}
}
Now I need to test them, and created unit test method, that must to check on 404 error:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration
class UnitControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
UnitService unitService;
#MockBean
UnitRepository unitRepository;
#Autowired
private UnitController unitController;
private List<Unit> units;
#Before
public void initUnits() {
units = new ArrayList<>();
Unit unitWithName = new Unit();
unitWithName.setId(1);
unitWithName.setUnitName("NameUnit");
units.add(unitWithName);
Unit unitWithoutName = new Unit();
unitWithoutName.setId(2);
units.add(unitWithoutName);
}
#Test
void contextLoads() {
Assert.assertNotNull(unitController);
}
#Test
void testGetAllUnits() throws Exception {
given(this.unitService.getAllUnits()).willReturn(units);
mockMvc.perform(get("/unit"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON));
}
#Test
void testUnitNotFound() throws Exception {
int id = -1;
given(this.unitRepository.findById(id)).willReturn(null);
mockMvc.perform(get("/unit/-1"))
.andExpect(status().isNotFound())
.andExpect(content().contentType(MediaType.APPLICATION_JSON));
}
}
When I run tests, testGetAllUnits fails:
java.lang.AssertionError: Content type not set
and testUnitNotFound fails with error:
java.lang.AssertionError: Status expected:<404> but was:<201>
But when I remove
#MockBean
UnitService unitService;
It will be working. What the problem?
UPDATE:
I have the similar problem now. This code inserts into database info about unit. But I made mock for the method.
#Test
void testAddUnit() throws Exception {
Unit unit = new Unit();
unit.setId(1);
unit.setUnitName("TestUnit");
given(unitService.addUnit("TestUnit")).willReturn(unit);
mockMvc.perform(post("/unit").param("unit_name", "TestUnit"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.unitName").value("TestUnit"))
.andExpect(jsonPath("$.id").value(1));
}
You're mocking the wrong bean. The bean throwing the exception is the service bean, so mock that.
#Test
void testUnitNotFound() throws Exception {
int id = -1;
given(this.service.getUnit(id)).willThrow(new EntityNotFoundException("Unit is not found"));
mockMvc.perform(get("/unit/-1"))
.andExpect(status().isNotFound())
.andExpect(content().contentType(MediaType.APPLICATION_JSON));
}
The problem with the testUnitNotFound() test not working is that you are expecting something from the mocked repository to happen inside a service which is also mocked.
If the service is mocked, then no implementation is invoked. Only a default value is returned which is null. And therefore no exception is thrown as expected...
If you want to have the flexibility of having most of the service mocked but having rest of them having their original implementations called, then you should change the:
#MockBean
UnitService unitService;
into
#SpyBean
UnitService unitService;
I've got a Spring Cloud FeignClient:
#FeignClient(name = "AccountSettingsClient", url = "${account.settings.service.url}", decode404 = true,
configuration = AccountSettingsClientConfig.class, fallbackFactory = AccountSettingsClientFallbackFactory.class)
public interface AccountSettingsClient {
#RequestMapping(method = RequestMethod.GET, value = "/settings/{uuid}",
headers = "User-Agent=page/okhttp", consumes = MediaType.APPLICATION_JSON_VALUE)
AccountSettings accountSettings(#PathVariable("uuid") String uuid);
}
The AccountSettingsClientConfig is:
#Configuration
#RequiredArgsConstructor
#EnableConfigurationProperties(SomeProperties.class)
#EnableFeignClients
public class AccountSettingsClientConfig {
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor() {
return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), resource());
}
}
Now in a integration test I need to mock the oauth2FeignRequestInterceptor bean and it doesn't work:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.NONE,
properties = {"account.settings.service.url=http://localhost:6565/users/accountsettings/"},
classes = { AccountSettingsClientConfig.class,
HttpMessageConvertersAutoConfiguration.class,
FeignAutoConfiguration.class, AccountSettingsClientIT.TestConfig.class })
#Slf4j
public class AccountSettingsClientIT {
#Inject
private AccountSettingsClient accountSettingsClient;
#TestConfiguration
static class TestConfig {
#Primary
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor() {
return mock(RequestInterceptor.class);
}
}
}
I tried also it with #MockBean, it was the same effect.
Any ideas how to fix it?