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.
Related
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.
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.
how can I create a "quasi" MVC integration test in Spring Boot. I would like to use my real implementation of a service, but I can't mange to do it. How can I inject real implementation instead of a mock.
My classes look like this
#Controller
#RequiredArgsConstructor
public class DashboardController {
private final RolesManagerService rolesManagerService;
private final ServletRequestManagerService servletRequestManagerService;
#GetMapping({"/", "/dashboard"})
public String index(Model model, HttpServletRequest httpServletRequest) {
model.addAttribute("canAddNewOrder", rolesManagerService.canRoleAccessApplicationPart(servletRequestManagerService.getRole(httpServletRequest), ApplicationPart.CREATE_NEW_ORDER));
model.addAttribute("var", "test");
return "dashboard";
}
}
and the test
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = DashboardController.class)
#AutoConfigureMockMvc
class IndexControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private UserDetailsService userDetailsService;
#MockBean
RolesManagerService rolesManagerService;
#MockBean
private ServletRequestManagerService servletRequestManagerService;
#Test
void testDashboard() throws Exception {
mockMvc.perform(get("/dashboard").with(user("admin").password("pass").roles("USER","ADMIN")))
.andExpect(status().isOk())
.andExpect(view().name("dashboard"))
.andExpect(xpath("//a").nodeCount(1))
.andExpect(model().attributeExists("canAddNewOrder"))
.andExpect(model().size(2))
.andExpect(model().attribute("var", equalTo("test")))
.andExpect(model().attribute("canAddNewOrder", equalTo(false)))
.andDo(print());
}
}
Generally WebMvcTest will not create a full Spring context with all components (Service etc) injected, but only the Controller you define. Either use a full SpringBootTest, or add something like this in your WebMvcTest class:
#TestConfiguration
static class AdditionalTestConfig {
#Bean
public RolesManagerService getService() {
return new RolesManagerService();
}
}
How to write JUnit Test cases for RestController, Service and DAO layer?
I've tried MockMvc
#RunWith(SpringRunner.class)
public class EmployeeControllerTest {
private MockMvc mockMvc;
private static List<Employee> employeeList;
#InjectMocks
EmployeeController employeeController;
#Mock
EmployeeRepository employeeRepository;
#Test
public void testGetAllEmployees() throws Exception {
Mockito.when(employeeRepository.findAll()).thenReturn(employeeList);
assertNotNull(employeeController.getAllEmployees());
mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/employees"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
How can I verify the CRUD methods inside the rest controller and other layers ?
You can use #RunWith(MockitoJUnitRunner.class) for unit testing with your Service Layer mocking your DAO Layer components. You don't need SpringRunner.class for it.
Complete source code
#RunWith(MockitoJUnitRunner.class)
public class GatewayServiceImplTest {
#Mock
private GatewayRepository gatewayRepository;
#InjectMocks
private GatewayServiceImpl gatewayService;
#Test
public void create() {
val gateway = GatewayFactory.create(10);
when(gatewayRepository.save(gateway)).thenReturn(gateway);
gatewayService.create(gateway);
}
}
You can use #DataJpaTest for integration testing with
your DAO Layer
#RunWith(SpringRunner.class)
#DataJpaTest
public class GatewayRepositoryIntegrationTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private GatewayRepository gatewayRepository;
// write test cases here
}
Check this article for getting more details about testing with Spring Boot
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();