I am trying to unit test my Spring Controller service that returns a String. I want to assert that the expected url name is correct but my test always returns a null url. I am using SpringRunner, #WebMvcTest and MockMvc for this.
#Slf4j
#Controller
public class CompanyInfoController {
private CompanyService companyService;
#Autowired
public CompanyInfoController(final CompanyService companyService) {
this.companyService = companyService;
}
#PreAuthorize("#someService.hasRole('" + Constants.MY_ROLE + "')")
#RequestMapping(value = "/companyInfo", method = RequestMethod.GET)
public String getCompanyInfo(final HttpServletRequest request, final
Model model) throws Exception {
log.debug("Getting Company Info");
final CompanyInfoDTO companyInformation = loadCompanyInfo(request);
model.addAttribute("companyInformation", companyInformation);
return "companyInfo";
}
private CompanyInfoDTO loadCompanyInfo(final HttpServletRequest request) throws Exception {
final Account someAccount= (Account)request.getAttribute("someAccount");
if (null != someAccount) {
final CompanyInfoDTO companyInformation = companyService.getCompanyInfo(someAccount);
return companyInformation;
} else {
throw new Exception("Unable to retrieve this account details.");
}
}
}
Controller Test -
#RunWith(SpringRunner.class)
#WebMvcTest(CompanyInfoController.class)
#Import(SecurityConfiguration.class)
public class CompanyInfoControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private CompanyService companyService;
#MockBean(name = "someService")
private SomeService someAuthorizationService;
#Before
public void setUp(){
Mockito.when(someAuthorizationService.hasRole(Constants.MY_ROLE))
.thenReturn(true);
}
#Test
#WithMockUser
public void canInvokeGetCompanyInfoServiceSuccessfully() throws Exception{
CompanyInfoDTO dto = new CompanyInfoDTO();
final Account mockAccount = new Account();
mockAccount.setId("12334");
Mockito.when(companyService.getCompanyInfo(Mockito.any(Account.class)))
.thenReturn(dto);
RequestBuilder request = get("/companyInfo")
.requestAttr("someAccount", mockAccount);
mockMvc.perform(get("/companyInfo"))
.andExpect(status().isOk());
}
#Test
#WithMockUser
public void forwardsToCompanyInfoPageSuccessfully() throws Exception{
CompanyInfoDTO dto = new CompanyInfoDTO();
final Account mockAccount = new Account();
mockAccount.setId("12334");
Mockito.when(companyService.getCompanyInfo(Mockito.any(Account.class)))
.thenReturn(dto);
RequestBuilder request = get("/companyInfo")
.requestAttr("someAccount", mockAccount);
mockMvc.perform(request)
.andExpect(redirectedUrl("companyInfo"));
}
}
The test keeps failing with this error -
java.lang.AssertionError: Redirected URL
Expected :companyInfo
Actual :null
Not sure what I am missing!
I do not see the debug statement inside my controller getting printed. But my controller is not mocked out. Is my security mocking correct? Any help is greatly appreciated.
Update - This is what I see in my logs -
MockHttpServletRequest:
HTTP Method = GET
Request URI = /companyInfo
Parameters = {}
Headers = []
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = org.cmt.controller.CompanyInfoController
Method = public java.lang.String org.cmt.controller.CompanyInfoController.getCompanyInfo(javax.servlet.http.HttpServletRequest,org.springframework.ui.Model) throws java.lang.Exception
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", X-Frame-Options:"SAMEORIGIN"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: No ModelAndView found
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:36)
I see Http response code 200. I am assuming it authenticates just fine. But why would my model and view be null?
Related
This is my CurriculoControllerTest.java class
#SpringBootTest
#ExtendWith(SpringExtension.class)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#DisplayName("Curriculo Controller Test")
#ActiveProfiles("local")
#AutoConfigureMockMvc
#Import(CurriculoController.class)
class CurriculoControllerTest {
private final String JSON_FORMAT = "application/json; charset=utf-8";
private final String BASE_PATH = "/curriculos";
#MockBean
private CurriculoServiceImpl curriculoService;
#SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
#Autowired
private MockMvc mockMvc;
public static CurriculoDTO createCurriculoInput() {
return CurriculoDTO.builder()
.id(UUID.randomUUID())
.dadosPessoais(DadosPessoaisDTO.builder()
.nome("joão")
.cargo("programador")
.email("joao#email.com")
.build())
.build();
}
CurriculoDTO novoCurriculo = CurriculoDTO.builder()
.id(UUID.randomUUID())
.dadosPessoais(DadosPessoaisDTO.builder()
.nome("Bruno")
.build())
.build();
CurriculoDTO curriculoExpected = CurriculoDTO.builder()
.id(UUID.randomUUID())
.dadosPessoais(DadosPessoaisDTO.builder()
.nome("Bruno")
.cargo("programador")
.email("joao#email.com")
.build())
.build();
#Test
#DisplayName("Deve retornar sucesso ao atualizar os dados pessoais do currículo")
public void deveRetornarSucessoAoAtualizarDadosPessoaisDoCurriculo() throws Exception {
var ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
var json = ow.writeValueAsString(curriculoExpected.getDadosPessoais());
doReturn(curriculoExpected).when(curriculoService)
.updateDadosPessoais(createCurriculoInput().getDadosPessoais(), novoCurriculo.getId());
mockMvc.perform(patch(BASE_PATH + "/dados-pessoais/" + createCurriculoInput().getId()).contentType(JSON_FORMAT).content(json))
.andExpect(status().isOk());
}
}
CurriculoController.java
#RestController
#RequestMapping("/curriculos")
public class CurriculoController {
private final DateTimeFormatter YYYY_MM_DD = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private final CurriculoServiceImpl service;
#Autowired
public CurriculoController(CurriculoServiceImpl service) {
this.service = service;
}
#PatchMapping("/dados-pessoais/{id}")
public ResponseEntity<CurriculoDTO> updateDadosPessoais(#RequestBody #Valid DadosPessoaisDTO dto,
#PathVariable UUID id) {
Optional<CurriculoDTO> curriculo = Optional.ofNullable(service.findById(id));
if (curriculo.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(service.updateDadosPessoais(dto, id), HttpStatus.OK);
}
CurriculoServiceImpl
public CurriculoDTO updateDadosPessoais(DadosPessoaisDTO dto, UUID id) {
Optional<Curriculo> optCurriculo = repository.findById(id)
.map(curriculo -> {
curriculo.setNome(Objects.nonNull(dto.getNome())
? dto.getNome() : curriculo.getNome());
curriculo.setCargo(Objects.nonNull(dto.getCargo())
? dto.getCargo() : curriculo.getCargo());
curriculo.setEmail(Objects.nonNull(dto.getEmail())
? dto.getEmail() : curriculo.getEmail());
curriculo.setSumario(Objects.nonNull(dto.getSumario())
? dto.getSumario() : curriculo.getSumario());
curriculo.setLinguagem(Objects.nonNull(dto.getLinguagem())
? dto.getLinguagem() : curriculo.getLinguagem());
return repository.save(curriculo);
});
CurriculoDTO curriculoDTO = converter.mapCurriculoToCurriculoDTO(optCurriculo.orElse(null));
curriculoDTO.setDadosPessoais(dto);
return curriculoDTO;
}
I've tried dozens of different ways, but I keep getting the 404 error, even though my URL is correct, could it be because the ID is not being found?
java.lang.AssertionError: Status expected:<200> but was:<404>
Expected :200
Actual :404
You mocked CurriculoServiceImpl but haven't stubbed service.findById(id) - you get an empty curriculo and return HttpStatus.NOT_FOUND.
As a side note - you seem to be testing only one controller mocking a service it depends on - you may want to consider #WebMvcTest instead of #SpringBootTest
I have an issue when I try to run my controller's unit test class. I get always a empty body in the response and I don't manage to find why.
I put here the code. Maybe someone with an external vision will be able to see the reason.
the controller:
#ResponseBody
#PostMapping(path = "/upload", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<Object> uploadFile(#RequestParam("file") MultipartFile multipartFileData, #RequestParam(name="jobId", required = false) String jobId) {
JobStatus result;
try {
result = this.fileService.uploadFileChunk(multipartFileData, 1, 1, jobId);
}catch (ExecutionException|InterruptedException|IOException ex){
Thread.currentThread().interrupt();
return new ResponseEntity<>(ex,HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<>(result,HttpStatus.OK);
}
the unit test class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes= FileUploadServiceRestController.class)
public class FileUploadServiceControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#MockBean
private FileUploadServiceImpl fileService;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
#Test
public void testUploadFile()
throws Exception {
MockMultipartFile file
= new MockMultipartFile(
"file",
"hello.txt",
MediaType.TEXT_PLAIN_VALUE,
"Hello, World!".getBytes()
);
JobStatus job = new JobStatus("uuid", ConstantUtil.JOB_STARTED);
when(fileService.uploadFileChunk(Mockito.any(MultipartFile.class),Mockito.eq(1),Mockito.eq(1),Mockito.isNull())).thenReturn(job);
mockMvc.perform(MockMvcRequestBuilders.multipart("/file/upload").file(file))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk());
}
}
and the object which will be transfered:
public class JobStatus implements Serializable {
private static final long serialVersionUID = -4405865740177389860L;
private String jobId;
private String status;
public JobStatus() {
}
public JobStatus(String jobId, String status) {
this.jobId = jobId;
this.status = status;
}
public String getJobId() {
return jobId;
}
public void setJobId(String jobId) {
this.jobId = jobId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
For information, this controller works well when I call it from the client. I can see that the mock is well returned when I put a breakpoint at the end of the controller, but the response body stay empty.
I add here the result of print if it could help:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /file/upload
Parameters = {}
Headers = [Content-Type:"multipart/form-data"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = org.iso.fileservice.controller.FileUploadServiceRestController
Method = org.iso.fileservice.controller.FileUploadServiceRestController#uploadFile(MultipartFile, String)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotWritableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 500
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
thanks, Mathieu
Just ran into this today.
I found the answer on Why MockMvc request retrieve empty responseBody while test succeed?
I just added My restController with #Autowired instead of #InjectMocks, after that I started to retrieve the Response Entity instead of a 500 status response
What is the proper way to junit mono and get a body response?
I expected "Single Item Only" to be in the body response of MockHttpServletResponse. But instead I see it's returned in Async.
Sample Method:
public Mono<String> getData(final String data) {
return this.webClient.get().uri("/dataValue/" + data)
.retrieve()
.bodyToMono(String.class);
}
Sample Junit:
#WebMvcTest(DataController.class)
public class DataMockTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private Service service;
#Test
public void getDataTest() throws Exception {
Mono<String> strMono = Mono.just("Single Item Only");
when(service.getData("DATA")).thenReturn(strMono);
this.mockMvc.perform(get("/some/rest/toget/data")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status()
.isOk());
}
Test Response:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /some/rest/toget/data
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json"]
Body = null
Session Attrs = {}
Async:
Async started = true
Async result = Single Item Only
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Updated Junit with StepVerifier:
#WebMvcTest(DataController.class)
public class DataMockTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private Service service;
#Test
public void getDataTest() throws Exception {
Mono<String> strMono = Mono.just("Single Item Only");
when(service.getData("DATA")).thenReturn(strMono);
//Added this which verifies the mono method. How to mock it so the API would return the mono value?
StepVerifier.create(service.getDATA("DATA"))
.assertNext(data -> {
assertNotNull(data);
assertEquals("Single Item Only", data);
}).verifyComplete();
this.mockMvc.perform(get("/some/rest/toget/data")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status()
.isOk());
}
It seems to me that what you are looking forward is StepVerifier. With it you cam assert the content of Mono an Flux data types. Please refer to https://www.baeldung.com/reactive-streams-step-verifier-test-publisher.
My controller has this code
#RequestMapping(value = "/getUserByEmailId/{emailId}", method = GET, produces = "application/json")
public ResponseEntity<UserRegistrationResponse> getUserByEmailId(#PathVariable String emailId) throws ServiceException {
return new ResponseEntity(generateUserOutput(userService.findUserByEmail(emailId)), OK);
}
My test has this:
#Test
public void testGetUserByEmailId() throws Exception {
when(userService.findUserByEmail(any())).thenReturn(userOutput);
MvcResult mvcResult = performGet("/user/getUserByEmailId/{emailId}", userRegistrationRequest.getEmail());
assertEquals(200, mvcResult.getResponse().getStatus());
}
private MvcResult performGet(String path, String pathVariable) throws Exception {
return mockMvc.perform(get(path,pathVariable)
.accept(APPLICATION_JSON))
.andDo(print())
.andReturn();
}
This is returning HTTP status code 406.
MockHttpServletRequest:
HTTP Method = GET
Request URI = /user/getUserByEmailId/testEmailId#test.com
Parameters = {}
Headers = {Accept=[application/json]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.nz.unicorn.web.controller.UserController
Method = public org.springframework.http.ResponseEntity<com.nz.unicorn.web.response.UserRegistrationResponse> com.nz.unicorn.web.controller.UserController.getUserByEmailId(java.lang.String) throws com.nz.unicorn.service.exception.ServiceException
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotAcceptableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 406
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Is there anything I can do? I have seen a lot of questions without an answer that I can use. Do note, mine is not a spring test, it's a unit test.
This is a workaround I did, changed the controller as follows and it works:
#RequestMapping(value = "/getByEmailId/{emailId}", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(OK)
public UserRegistrationResponse getUserByEmailId(#PathVariable String emailId) throws ServiceException {
return generateUserOutput(userService.findUserByEmail(emailId));
}
This solved, don't know why ResponseEntity still didn't work though.
I am developing a microservice application and I need to test a post request
to a controller. Testing manually works but the test case always returns null.
I've read many similar questions here in Stackoverflow and documentation but haven't figured out yet what I am missing.
Here is what I currently have and what I tried in order to make it work:
//Profile controller method need to be tested
#RequestMapping(path = "/", method = RequestMethod.POST)
public ResponseEntity<Profile> createProfile(#Valid #RequestBody User user, UriComponentsBuilder ucBuilder) {
Profile createdProfile = profileService.create(user); // line that returns null in the test
if (createdProfile == null) {
System.out.println("Profile already exist");
return new ResponseEntity<>(HttpStatus.CONFLICT);
}
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ucBuilder.path("/{name}").buildAndExpand(createdProfile.getName()).toUri());
return new ResponseEntity<>(createdProfile , headers, HttpStatus.CREATED);
}
//ProfileService create function that returns null in the test case
public Profile create(User user) {
Profile existing = repository.findByName(user.getUsername());
Assert.isNull(existing, "profile already exists: " + user.getUsername());
authClient.createUser(user); //Feign client request
Profile profile = new Profile();
profile.setName(user.getUsername());
repository.save(profile);
return profile;
}
// The test case
#RunWith(SpringRunner.class)
#SpringBootTest(classes = ProfileApplication.class)
#WebAppConfiguration
public class ProfileControllerTest {
#InjectMocks
private ProfileController profileController;
#Mock
private ProfileService profileService;
private MockMvc mockMvc;
private static final ObjectMapper mapper = new ObjectMapper();
private MediaType contentType = MediaType.APPLICATION_JSON;
#Before
public void setup() {
initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(profileController).build();
}
#Test
public void shouldCreateNewProfile() throws Exception {
final User user = new User();
user.setUsername("testuser");
user.setPassword("password");
String userJson = mapper.writeValueAsString(user);
mockMvc.perform(post("/").contentType(contentType).content(userJson))
.andExpect(jsonPath("$.username").value(user.getUsername()))
.andExpect(status().isCreated());
}
}
Tried to add when/thenReturn before post but still returns 409 response with null object.
when(profileService.create(user)).thenReturn(profile);
You're using a mock profileService in your test, and you never tell that mock what to return. So it returns null.
You need something like
when(profileService.create(any(User.class)).thenReturn(new Profile(...));
Note that using
when(profileService.create(user).thenReturn(new Profile(...));
will only work if you properly override equals() (and hashCode()) in the User class, because the actual User instance that the controller receives is a serialized/deserialized copy of the user you have in your test, and not the same instance.