Working on a spring boot based Rest project I have a controller like this
which calls service and service layer call dao layer. Now I am writing unit test code for controllers. when I run this the error says
java.lang.AssertionError: expected:<201> but was:<415>
I don't know where I am doing wrong:
public class CustomerController {
private static final Logger LOGGER = LogManager.getLogger(CustomerController.class);
#Autowired
private CustomerServices customerServices;
#Autowired
private Messages MESSAGES;
#Autowired
private LMSAuthenticationService authServices;
#RequestMapping(value = "/CreateCustomer", method = RequestMethod.POST)
public Status createCustomer(#RequestBody #Valid Customer customer, BindingResult bindingResult) {
LOGGER.info("createCustomer call is initiated");
if (bindingResult.hasErrors()) {
throw new BusinessException(bindingResult);
}
Status status = new Status();
try {
int rows = customerServices.create(customer);
if (rows > 0) {
status.setCode(ErrorCodeConstant.ERROR_CODE_SUCCESS);
status.setMessage(MESSAGES.CUSTOMER_CREATED_SUCCESSFULLY);
} else {
status.setCode(ErrorCodeConstant.ERROR_CODE_FAILED);
status.setMessage(MESSAGES.CUSTOMER_CREATION_FAILED);
}
} catch (Exception e) {
LOGGER.info("Cannot Create the Customer:", e);
status.setCode(ErrorCodeConstant.ERROR_CODE_FAILED);
status.setMessage(MESSAGES.CUSTOMER_CREATION_FAILED);
}
return status;
}
}
The test for the CustomerController.
public class CustomerControllerTest extends ApplicationTest {
private static final Logger LOGGER = LogManager.getLogger(CustomerControllerTest.class);
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#MockBean
private CustomerController customerController;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
Status status = new Status(200,"customer created successfully","success");
String customer = "{\"customerFullName\":\"trial8900\",\"customerPhoneNumber\": \"trial8900\", \"customerEmailID\": \"trial8900#g.com\",\"alternateNumber\": \"trial8900\",\"city\": \"trial8900\",\"address\":\"hsr\"}";
#Test
public void testCreateCustomer() throws Exception {
String URL = "http://localhost:8080/lms/customer/CreateCustomer";
Mockito.when(customerController.createCustomer(Mockito.any(Customer.class),(BindingResult) Mockito.any(Object.class))).thenReturn(status);
// execute
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post(URL)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(TestUtils.convertObjectToJsonBytes(customer))).andReturn();
LOGGER.info(TestUtils.convertObjectToJsonBytes(customer));
// verify
MockHttpServletResponse response = result.getResponse();
LOGGER.info(response);
int status = result.getResponse().getStatus();
LOGGER.info(status);
assertEquals(HttpStatus.CREATED.value(), status);
}
}
HTTP status 415 is "Unsupported Media Type". Your endpoint should be marked with an #Consumes (and possibly also #Produces) annotation specifying what kinds of media types it expects from the client, and what kind of media type it returns to the client.
Since I see your test code exercising your production code with MediaType.APPLICATION_JSON_UTF8, you should probably mark your endpoint as consuming and producing APPLICATION_JSON_UTF8.
Then you also need to make sure that there is nothing terribly wrong going on in your error handling, because in the process of catching the exceptions generated by your production code and generating HTTP responses, your error handling code may be generating something different, e.g. generating an error status response with a payload containing an HTML-formatted error message, which would have a content-type of "text/html", which would not be understood by your test code which expects json.
Use the below base test class for your setUp and converting json to string and string to json
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = Main.class)
#WebAppConfiguration
public abstract class BaseTest {
protected MockMvc mvc;
#Autowired
WebApplicationContext webApplicationContext;
protected void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
protected String mapToJson(Object obj) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(obj);
}
protected <T> T mapFromJson(String json, Class<T> clazz)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, clazz);
}
}
Also verify that your post call has happened or not check the below sample
Mockito.doNothing().when(customerServices).create(Mockito.any(Customer.class));
customerServices.create(customer);
Mockito.verify(customerServices, Mockito.times(1)).create(customer);
RequestBuilder requestBuilder = MockMvcRequestBuilders.post(URI)
.accept(MediaType.APPLICATION_JSON).content(inputInJson)
.contentType(MediaType.APPLICATION_JSON);
MvcResult mvcResult = mvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
assertEquals(HttpStatus.OK.value(), response.getStatus());
Related
I have this following custom interceptor in my java spring application as below.
public class AuthInterceptor implements ClientHttpRequestInterceptor {
private final String encodedCredentials;
public AuthInterceptor(String username, String password) {
this(username, password, (Charset) null);
}
public AuthInterceptor(String username, String password, #Nullable Charset charset) {
this.encodedCredentials = HttpHeaders.encodeBasicAuth(username, password, charset);
}
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = request.getHeaders();
headers.setBasicAuth(this.encodedCredentials);
return execution.execute(request, body);
}
}
But I am looking to find for some documentation on how to write a unit test for this class and could not find. Any input on how to test would be really helpful
Assuming you only want to test the interception and you already have set up mockito:
#Test
#DisplayName("Should add correct header to authorization")
void encodeProperly() throws Exception {
AuthInterceptor cut = new AuthInterceptor("someuserName","somePaswwordName");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
cut.intercept(request, null,null,mockedExecution );
ArgumentCaptor<HttpRequest> argumentCapture = ArgumentCaptor.forClass(HttpRequest.class) ;
verify(mockedExecution.execute(argumentCapture.capture(),any()));
assertEquals(expectedEncodedCredentials,argumentCapture.getValue().getHeaders().get(HttpHeaders.AUTHORIZATION));
}
so the steps are:
create your class to test cut
then create a mock of MockHttpServletRequest it will be helpful to perform your check
Unit test that class the same way you unit test any class.
classToTest = new AuthInterceptor()
Call the method with Mock objects.
Some example code:
#ExtendWith(MockitoExtension.class)
public class TestAuthInterceptor
{
private AuthInterceptor classToTest;
#Mock
private ClientHttpRequestExecution mockClientHttpRequestExecution;
#Mock
private ClientHttpResponse mockClientHttpResponse;
#Mock
private HttpHeaders mockHttpHeaders;
#Mock
private HttpRequest mockHttpRequest;
#BeforeEach
public void beforeEach()
{
classToTest = new AuthInterceptor("username", "password");
}
#Test
public void intercept_allGood_success()
{
final ClientHttpResponse actualResult;
final byte[] inputBody = null;
doReturn(mockHttpHeaders).when(mockHttpRequest).getHeaders();
doReturn(mockClientHttpResponse).when(mockClientHttpRequestExecution).execute(mockHttpRequest, inputBody);
actualResult = classToTest.intercept(mockHttpRequest, inputBody, mockClientHttpRequestExecution);
assertSame(mockClientHttpResponse, actualResult);
// other asserts as desired.
}
}
This is the first time I'm trying to write Integration Tests with the spring framework test. The controller returns a proper response in Postman but MvcResult returns "application/json;charset=UTF-8" as a response. I tried different solutions present on Internet but nothing seems to work. Please, help me with this.
I want to extract the key-value pair from the JSON response.
spring test = 5.1.8.RELEASE
Controller
#RestController
public class CustAuController {
#RequestMapping(value = "/retrieve_something", method = RequestMethod.GET)
retrieveSomething(){
}
}
Postman Response
{
"total_count" : 3,
"data" : {
"list" : [
{
...
}
]
}
}
Test Class
#SpringBootApplication
#SpringBootTest(classes = ServiceApplication.class)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class ControllerTest {
#Autowired
WebApplicationContext webApplicationContext;
MockMvc mockMvc;
public static String asJsonString(final Object obj) {
try {
final ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#BeforeAll
void setMockMvc() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
#DisplayName("Retrieve Something")
#Order(1)
void testRetrieveSomething() throws Exception{
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(
"/retrieve_something"))
.andExpect(status().isOk()).andReturn();
System.out.println(
result.getResponse().getContentType()); // returns application/json;charset=UTF-8
}
}
Change in the last line getContentType() to getContentAsString()
So, I have this unit test that I need to run.
#MockBean
private AppServiceImpl appService;
#Test
public void shouldThrowExceptionWhenAppIdIsNull() throws Exception {
File inputFile = this.getJsonFile();
RequestDto requestDto = objectMapper.readValue(inputFile.getAbsoluteFile(),
RequestDto.class);
AppData appData = requestDto.getAppData();
appData.setAppId(null);
requestDto.setAppData(appData);
when(appService.getUrl(requestDto, "header")).thenThrow(new RequestNotValidException());
String payload = objectMapper.writeValueAsString(requestDto);
this.mockMvc.perform(post(Base_URL + "app/requesturl")
.contentType(contentType).content(payload).header(this.Header, "header"))
.andExpect(status().is4xxClientError());
}
Interface for service:
SO when I run this test, it throws an exception and doesn't actually assert the test here.
I have added #ResponseStatus(HttpStatus.BAD_REQUEST) on top of RequestNotValidException and it extends RunTimeException
And in the second test case, I get empty response. I tried this API vis Postman and I get the response. Everything works fine there.
#Test
public void getCardRegistration() throws Exception {
File inputFile = this.getJsonFile();
RequestDto requestDto = objectMapper.readValue(inputFile.getAbsoluteFile(), RequestDto.class);
ResponseDto responseDto = new ResponseDto();
responseDto.setURL(AuthUtils.randomStringToken(35));
given(appService.getRegistrationUrl(requestDto, "header")).willReturn(responseDto);
String payload = objectMapper.writeValueAsString(requestDto);
MvcResult mvcResult = this.mockMvc.perform(post(Base_URL + "app/requesturl")
.contentType(contentType).content(payload).header(this.Header, "header"))
.andReturn();
String contentAsString = mvcResult.getResponse().getContentAsString();
}
Controller content:
#Autowired
IAppService appService;
#RequestMapping(value = "/app/requesturl", method = RequestMethod.POST)
public ResponseDto getCardsRegistration(#RequestBody #Valid RequestDto requestDto, #RequestHeader(value="X-App-Name", required = true) String header) throws RequestNotValidException, JsonProcessingException {
log.info("Request received in controller: "+ mapper.writeValueAsString(cardRegistrationRequestDto));
log.info("Header value: "+ header);
return this.appService.getRegistrationUrl(requestDto, header);
}
Test Class:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class AppRestControllerTest {
protected String Base_URL = "/app";
protected String Header = "X-App-Name";
protected MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
#Autowired
protected MockMvc mockMvc;
protected ObjectMapper objectMapper = new ObjectMapper();
#MockBean
private AppServiceImpl appService;
#Mock
private AppRegistrationRepository appRegistrationRepository;
#Before
public void setUp() throws Exception {
MapperFacade mapperFacade = new DefaultMapperFactory.Builder().build().getMapperFacade();
appService = new AppServiceImpl(appRegistrationRepository, mapperFacade);
}
What did I miss here?
Try to use
#RunWith(SpringRunner.class)
#WebMvcTest(YourController.class)
public class AppRestControllerTest {
Or
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#AutoConfigureMockMvc
public class AppRestControllerTest {
In your tests
I am testing an #RestController which has an API endpoint such as /api/dataobject. If the object (in JSON format) that is posted to this endpoint is missing some part of its meta data, the API should respond with a Http status of bad request (400).
When testing it through Postman, this works, however in my unit test where the controller is mocked it still returns a status 200.
The method in the RestController:
#RequestMapping("/api/dataobject")
public ResponseEntity postDataObject(#RequestBody final DataObject dataObject) throws InvalidObjectException {
if (!dataObjectValidator.validateDataObject(dataObject)) {
throw new InvalidObjectException("Data object was invalid: " + dataObject.toString());
}
return new ResponseEntity(HttpStatus.OK);
}
The InvalidObjectException is caught by a class annotated with #ControllerAdvice which extends ResponseEntityExceptionHandler and is handled as follows:
#ExceptionHandler(value = InvalidObjectException.class)
protected ResponseEntity<Object> handleInvalidObject(final InvalidObjectException exception, final WebRequest request) {
final String bodyOfResponse = exception.getMessage();
return handleExceptionInternal(exception, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
Now, the unit test class is as follows:
#RunWith(SpringRunner.class)
#WebMvcTest(DataObjectController.class)
public class DataObjectControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private DataObjectController dataObjectController;
private final String uri = "/api/idataobject";
#Test
public void noAppName() throws Exception {
DataObject object = getDataObjectNoAppName();
final String body = new Gson().toJson(object);
given(dataObjectController.postDataObject(object)).willReturn(new ResponseEntity(HttpStatus.BAD_REQUEST));
mvc.perform(post(uri)
.content(body)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
}
Even though the object is invalid, and I've said that the given object would return a HttpStatus 400, I get a 200 status in return.
Clearly I'm missing something here, but what?
The following test fails when I try to integrate spring session.
class WeChatOAuth2AuthenticationFilterTest extends AbstractWebMvcTest {
#Test
void it_should_redirect_user_to_origin_uri_when_wechat_oauth_is_finished() throws Exception {
String code = "codeToExchangeWeChatUserAccessToken"
String plainUrl = "http://www.example.com/index.html?a=b#/route"
String state = Base64.getUrlEncoder().encodeToString(plainUrl.getBytes("UTF-8"))
WxMpOAuth2AccessToken accessToken = new WeChatUserOAuth2AccessTokenFixture().buildToken()
given(wxMpService.oauth2getAccessToken(code))
.willReturn(accessToken)
this.mockMvc.perform(get("/wechat/oauth/token")
.param("state", state)
.param("code", code))
.andDo(print())
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl(plainUrl))
.andExpect(authenticated())
// throws Authentication should not be null
}
}
#Configuration
#EnableSpringHttpSession
public class HttpSessionConfig {
#Bean
protected SessionRepository sessionRepository() {
return new MapSessionRepository();
}
}
After some debugging, I find out that it is probably due to I cannot get HttpSession
// org.springframework.security.web.context.HttpSessionSecurityContextRepository
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
//returns null with spring-session,
//returns a MockHttpSession instance without spring-session
SecurityContext context = readSecurityContextFromSession(httpSession);
Currently, I make the spring session disabled for the tests with #ConditionalProperties. Any better idea is welcome.
This is related to correct setup of you mockMvc object in your test.
For brevity, I assume you can use #SpringBootTest annotation in your project. The codes below shows how you could properly wire in spring-session related classes into your mockMvc.
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class ExampleControllerV2SpringSessionTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private SessionRepository sessionRepository;
#Autowired
private SessionRepositoryFilter sessionRepositoryFilter;
//this is needed to test spring-session specific features
private MockMvc mockMvcWithSpringSession;
#Before
public void setup() throws URISyntaxException {
this.mockMvcWithSpringSession = MockMvcBuilders
.webAppContextSetup(wac)
.addFilter(sessionRepositoryFilter)
.build();
}
//------------------------- BEGIN: Test cases with Spring-session---------------------------------
#Test
public void getANewSpringSession(String requestBody) throws Exception {
MvcResult result = mockMvcWithSpringSession.perform(
get("/v1/sampleendpoint") .header("YOUR_HEADER_NAME", "YOUR_HEADER_VALUE")
.contentType(MediaType.APPLICATION_JSON)
.content("YOUR_SAMPLE_JSON_BODY"))
.andExpect(status().isOk())
.andExpect(header().string("x-auth-token", notNullValue()))
.andReturn();
String xAuthToken = result.getResponse().getHeader(AuthenticationControllerV2.Params.SESSION_KEY);
MapSession curSession = (MapSession) sessionRepository.getSession(xAuthToken);
Assert.assertNotNull(curSession);
}
//------------------------- END: Test cases with Spring-session---------------------------------
}