MockMvc REST controller - always returns empty body - java

I would like to test my REST controller using MockMvc but I always get an empty body response.
My AccountControllerUnitTest looks like this:
#RunWith(SpringRunner.class)
#WebMvcTest(AccountRestController.class)
public class AccountRestControllerUnitTests {
#Autowired
private MockMvc mockMvc;
#MockBean(name = "dtoUtil")
private DtoUtil dtoUtil;
#MockBean
private AccountService accountService;
#Test
public void canRetrieveAll() throws Exception {
when(accountService.findAll())
.thenReturn(Collections.singletonList(AccountTestFixture.createAccount()));
this.mockMvc.perform(get("/accounts")).andDo(print())
.andExpect(status().isOk());
}}
The accountService when mock works as expected, calling accountService.findAll() returns a list with a single account element.
With my used AccountRestController being:
#RestController
#AllArgsConstructor
#RequestMapping("/accounts")
public class AccountRestController {
private AccountService accountService;
#Qualifier(dtoUtil)
private DtoUtil dtoUtil;
#GetMapping
public List<AccountDto> getAccounts() {
return accountService.findAll().stream()
.map(dtoUtil::mapToDto)
.collect(Collectors.toList());
}
Running the test will result in a MockHttpServletResponse with a body that is null.
It works flawlessly for my normal (non-rest that has a model) controller, the only difference being that it doesn't use the DtoUtil.
Could that be the reason for it constantly returning null?
edit:
The DtoUtil:
#Component
public class DtoUtil{
#Autowired
private ModelMapper mapper;
public AccountDto mapToDto(Account account) {
return modelMapper.map(account, AccountDto.class);
}
}

Add to your test
when(dtoUtil.mapToDto(...)).thenCallRealMethod();

Related

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.

Junit5: WebMvcTest returns 404. Probably because I'm not mocking underlyng method?

Preamble: I'm learning Java, Spring Boot and overall... TDD with Java/Spring Boot.
Used versions:
Spring Boot 2.6.3
Java 17
Junit5
This is my controller:
#RestController
#RequestMapping("/api/v1/login")
public class LoginController {
#Autowired
private JwtAuthentication jwtAuthentication;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private JwtUserDetailService jwtUserDetailService;
#PostMapping
public ResponseEntity<?> createAuthenticationToken(#RequestBody UserEntity userEntity) throws Exception {
jwtAuthentication.authenticate(userEntity.getUsername(), userEntity.getPassword());
final UserDetails userDetails = jwtUserDetailService.loadUserByUsername(userEntity.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
}
Relevant autowired is the JwtAuthentication:
#Component
public class JwtAuthentication {
#Autowired
private AuthenticationManager authenticationManager;
private static final long serialVersionUID = -20220210203900L;
public void authenticate(String username, String password) throws BadCredentialsException {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (BadCredentialsException e) {
throw new BadCredentialsException("INVALID_CREDENTIALS", e);
}
}
}
I wrote the test for JwtAuthentication itself without issues, now I need to test the Controller.
This is my test:
#ExtendWith(SpringExtension.class)
#WebMvcTest(LoginControllerTest.class)
class LoginControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private JwtAuthentication jwtAuthentication;
#Test
void testCanLogin() throws Exception {
UserEntity userEntity = new UserEntity();
userEntity.setUsername("username");
userEntity.setPassword("password");
mvc.perform(post("/api/v1/login/").contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(userEntity))).andExpect(status().isOk());
}
}
But I get 404 instead of 200.
I read that reason is missing mocking underlying methods. That, in reality, these tests on Controller doesn't launch entire configuration (and so on). Cannot find the answer atm, here on S.O..
So, I think the solution need to be add a "simple" when in test:
#Test
void testCanLogin() throws Exception {
UserEntity userEntity = new UserEntity();
userEntity.setUsername("username");
userEntity.setPassword("password");
// I think I need some code here:
// pseudocode when(jwtAuthentication.authenticate("username", "password").then .... I DON'T KNOW HERE!
mvc.perform(post("/api/v1/login/").contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(userEntity))).andExpect(status().isOk());
}
404 is because of no controllers to handle the request.
It is because you specify a wrong controller in #WebMvcTest such that the controller that you want to test is not included in the spring container. So change to the following should fix the problem :
#WebMvcTest(LoginController.class)
class LoginControllerTest {
#MockBean
private JwtAuthentication jwtAuthentication;
#MockBean
private JwtTokenUtil jwtTokenUtil;
#MockBean
private JwtUserDetailService jwtUserDetailService;
}
Also note the following points :
I remove #ExtendWith(SpringExtension.class) as #WebMvcTest already included it
#WebMvcTest will only enable the beans related to web layers (see this) which JwtTokenUtil and JwtUserDetailService does not belong to it. So you have to use #MockBean to mock them.

Use #WebMvcTest without #MockBean in Spring Boot

I am working on an assignment where a test case is already provided and I cannot change it in any sense whatsoever.
I have created a simple web project using spring boot whose controller looks like :
Controller.java
#RestController
#RequestMapping("/person")
public class Controller {
private final PersonService personService;
#Autowired
public Controller(final PersonService personService) {
this.personService = personService;
}
#PostMapping
public ResponseEntity<PersonResponse> testPerson(#RequestBody PersonRequest personRequest) {
return new ResponseEntity<>(personService.clearPastHistory((personRequest)), HttpStatus.OK);
}
}
And the Service layer is :
Service.java
#Service
public class PersonServiceImplementation implements PersonService {
#Override
public PersonResponse clearPastHistory(PersonRequest requestDTO) {
//business logic here
}
ControllerTest.java
Here, the BASE_REQUEST is a json string which acts as an input from the user.
#WebMvcTest(Controller.class)
class ControllerTest {
private static final MockHttpServletRequestBuilder REQUEST_BUILDER =
request(HttpMethod.POST, "/person")
.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.accept(APPLICATION_JSON);
#Autowired
private MockMvc mockMvc;
#Test
void callCorrectEndpoint() throws Exception {
mockMvc.perform(REQUEST_BUILDER.content(BASE_REQUEST))
.andExpect(status().isOk());
}
Now the issue is I cannot build the project without using
#MockBean
private PersonService personService;
in my test class.
and if I am using this and submitting to an online portal, the automated tests in the portal are failing with an error that Testcase was modified.
Is there a way possible to achieve this without modifying the test case at all ?
Would be highly thankful for any help.

Mockito when().thenReturn() Returning Null when it should return empty list

I've been trying to figure out why my mocked findIngredientsByCategory method is returning null when I have when(controller.findIngredientsByCategory(any()).thenReturn(Collections.emptyList()). This implementation works for the findAll method works.
Below is my implementation for my unit test:
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(IngredientController.class)
#ContextConfiguration(classes = {TestContext.class, WebApplicationContext.class})
#WebAppConfiguration
public class IngredientControllerTest {
#Autowired
private WebApplicationContext context;
#Autowired
private MockMvc mvc;
#MockBean
private IngredientController ingredientController;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.webAppContextSetup(context).build();
}
#Autowired
private ObjectMapper mapper;
private static class Behavior {
IngredientController ingredientController;
public static Behavior set(IngredientController ingredientController) {
Behavior behavior = new Behavior();
behavior.ingredientController = ingredientController;
return behavior;
}
public Behavior hasNoIngredients() {
when(ingredientController.getAllIngredients()).thenReturn(Collections.emptyList());
when(ingredientController.getIngredientsByCategory(any())).thenReturn(Collections.emptyList());
when(ingredientController.getIngredientById(anyString())).thenReturn(Optional.empty());
return this;
}
}
#Test
public void getIngredientsByCategoryNoIngredients() throws Exception {
Behavior.set(ingredientController).hasNoIngredients();
MvcResult result = mvc.perform(get("/ingredients/filter=meat"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andReturn();
String content = result.getResponse().getContentAsString();
System.out.println(content);
}
And below is the implementation for the controller:
#RestController
#RequestMapping("/ingredients")
public class IngredientController {
#Autowired
private IngredientRepository repository;
#RequestMapping(value = "/filter", method = RequestMethod.GET)
public List getIngredientsByCategory(#RequestParam("category") String category) {
return repository.findByCategory(category);
}
}
I'm not sure why the mock controller is returning null with this request, when I tell it to return an empty list. If someone could please help with this I would greatly appreciate it! Thanks.
Th request path in test is "/ingredients/filter=meat", but it should be "/ingredients/filter?category=meat". So, it seem that getIngredientsByCategory was not called.
The MockMvc actually will call the IngredientController that is bootstrapped and created by the Spring Test framework but not call the mocked IngredientController that you annotated with #MockBean, so all the stubbing that you made will not be called.
Actually, the point of #WebMvcTest is to test #RestController and its related Spring configuration is configured properly , so a real instance of IngredientController is necessary to create rather than using a mocked one. Instead , you should mock the dependencies inside IngredientController (i.e IngredientRepository).
So , the codes should looks like:
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(IngredientController.class)
#ContextConfiguration(classes = {TestContext.class, WebApplicationContext.class})
#WebAppConfiguration
public class IngredientControllerTest {
#Autowired
private WebApplicationContext context;
#Autowired
private MockMvc mvc;
#MockBean
private IngredientRepository ingredientRepository;
#Test
public void fooTest(){
when(ingredientRepository.findByCategory(any()).thenReturn(Collections.emptyList())
//And use the MockMvc to send a request to the controller,
//and then assert the returned MvcResult
}
}

SpringBootTest/WebMvcTest not invoking validator on request body

I am trying to test a controller such that it triggers validation on the request body object based on the validators that I have defined on the object. Below are the relevant classes.
If you notice my test method I am expecting bad request based on the validator defined, but for some reason it directly tries to execute the service, which was not expected and therefore I don't have any mock defined for the service and hence, I get a NullPointerException since the service object is null as it is mocked.
I tried replacing WebMvcTest to SpringBootTest based on some suggestions that I googled and stackoverflowed, but nothing helped. What could be the possible reason?
Request Object
#Validated
public class RequestBean implements Serializable {
private static final long serialVersionUID = -8976713543478074839L;
#NotNull
private Long id;
#NotNull
private String name;
#NotEmpty
private List<Long> statusIds;
// getters and setters
}
Controller
#RestController
public class SomeController {
#Autowired
private SomeService service;
#PostMapping(value = "/resources", produces = MediaType.APPLICATION_JSON_VALUE)
public List<VerDealProductBean> fetchResources(
#Valid #RequestBody RequestBean request)
throws SomeException {
return service.doSomething(request);
}
Controller Test
#RunWith(SpringRunner.class)
#WebMvcTest(SomeController.class)
#ActiveProfiles("test")
public class SomeControllerTest {
private MockMvc mockMvc;
#MockBean
private SomeService service;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(SomeController.class)
.build();
}
#Test
public void testFetchResourcesWithNoInput() throws Exception {
String request = "{\"id\": null}";
mockMvc.perform(post("/resources")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(request))
.andDo(print())
.andExpect(status().isBadRequest());
}
}
Maybe you lack a package javax.el
http://hibernate.org/validator/documentation/getting-started/
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b09</version>
</dependency>

Categories

Resources