Testing #ModelAttribute method on a controller - spring

This are the annotated methods in the controller:
#RequestMapping(method = RequestMethod.GET)
public String getClient(#PathVariable("contractUuid") UUID contractUuid, Model model) {
ClientDto clientDto = new ClientDto();
clientDto.setContractUuid(contractUuid);
model.addAttribute("client", clientDto);
return "addClient";
}
#ModelAttribute("contract")
public ContractDto getContract(#PathVariable("contractUuid") UUID contractUuid) throws ContractNotFoundException {
return contractService.fromEntity(contractService.findByUuid(contractUuid));
}
The test method that I am trying is shown below, but it fails for attribute contract. The attribute client is added to the Model in a #RequestMapping method.
private MockMvc mockMvc;
#Autowired
private ContractService contractServiceMock;
#Autowired
private ClientService clientServiceMock;
#Autowired
protected WebApplicationContext wac;
#Before
public void setup() {
Mockito.reset(contractServiceMock);
Mockito.reset(clientServiceMock);
this.mockMvc = webAppContextSetup(this.wac).build();
}
#Test
public void test() throws Exception {
UUID uuid = UUID.randomUUID();
Contract contract = new Contract(uuid);
when(contractServiceMock.findByUuid(uuid)).thenReturn(contract);
mockMvc.perform(get("/addClient/{contractUuid}", uuid))
.andExpect(status().isOk())
.andExpect(view().name("addClient"))
.andExpect(forwardedUrl("/WEB-INF/pages/addClient.jsp"))
.andExpect(model().attributeExists("client"))
.andExpect(model().attributeExists("contract"));
}
The contract attribute shows in the jsp page when I run the application, since I use some of its attributes, but since it fails in the test method is there another way to test it ?
It fails with the message:
java.lang.AssertionError: Model attribute 'contract' does not exist
Spring is 4.0.1.RELEASE

It seems it was my fault.
Even though the #ModelAttribute method returns an instance of ContractDto I only mocked one method used from the service:
when(contractServiceMock.findByUuid(uuid)).thenReturn(contract);
and so findByUuid returned something, but contractService.fromEntity was left untouched so I had to also mock it:
UUID uuid = UUID.randomUUID();
Contract contract = new Contract(uuid);
ContractDto contractDto = new ContractDto(uuid);
when(contractServiceMock.findByUuid(uuid)).thenReturn(contract);
when(contractServiceMock.fromEntity(contract)).thenReturn(contractDto);

Related

Spring boot rest api mockito + mockmvc persistence

I would like to create Test for my rest controller:
#Controller
#RequestMapping("/v2/api/show/project")
public class ApiAccessController {
private final ApiAccessService apiAccessService;
#Autowired
ApiAccessController(ApiAccessService apiAccessService){
this.apiAccessService = apiAccessService;
}
#PutMapping(value = "/{id}/apikey")
public ResponseEntity<ApiKeyResponse> generateApiKey(#PathVariable("id")Long id, Principal principal) {
return apiAccessService.generateApiKey(id, principal.getName());
}
}
My test looks as follow:
#RunWith(SpringJUnit4ClassRunner.class)
public class ApiAccessControllerTest {
private MockMvc mockMvc;
Principal principal = new Principal() {
#Override
public String getName() {
return "TEST_PRINCIPAL";
}
};
#InjectMocks
ApiAccessController apiAccessController;
#Mock
ProjectRepository projectRepository;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.standaloneSetup(apiAccessController).build();
}
#Test
public void testGenerateApiKey() throws Exception {
Project project = new Project();
project.setId((long) 1);
project.setName("test");
project.setDescription("testdesc");
project.setCiid("ciid");
when(projectRepository.save(any(Project.class))).thenReturn(project);
mockMvc.perform(MockMvcRequestBuilders.put("/v2/api/show/project/" + project.getId() +"/apikey").principal(principal))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
Which is ment to create project and then run generateApiKey on this project, however I get NullpointerException looking like mocked controller cannot find created entity
could anyone please point me in the right direction as I am just starting with testing?
You should mock ApiAccessService instead of ProjectRepository.
Have a look at the code:
#RunWith(SpringJUnit4ClassRunner.class)
public class ApiAccessControllerTest {
private MockMvc mockMvc;
private Principal principal = () -> "TEST_PRINCIPAL";
#InjectMocks
private ApiAccessController apiAccessController;
#Mock
private ApiAccessService apiAccessService;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(apiAccessController).build();
}
#Test
public void testGenerateApiKey() throws Exception {
long id = 1L;
when(apiAccessService.generateApiKey(id, principal.getName())).thenReturn(new ApiKeyResponse(111L));
mockMvc.perform(MockMvcRequestBuilders.put("/v2/api/show/project/{id}/apikey", id).principal(principal))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
If you want to create integration test, that tests ApiAccessController -> ApiAccessService -> ProjectRepository integration you need to load your context (use for example #SpringBootTest).
Also you need to fix controller, use ResponseEntity.ok(...) :
#PutMapping(value = "/{id}/apikey")
public ResponseEntity<ApiKeyResponse> generateApiKey(#PathVariable("id") Long id, Principal principal) {
return ResponseEntity.ok(apiAccessService.generateApiKey(id, principal.getName()));
}
You can find really good examples of all test types in this repository MVC tests examples
The Mock you are creating is not referenced in the Controller. The Service you reference in the Controller is not part of your test setup. Therefore any access to the Service will cause a NullPointerException as the Service is not set.

How to mock BindingResult in Spring Boot Test

I have the following controller:
#RestController
#RequestMapping(value = ROOT_MAPPING)
public class GatewayController {
#Autowired
private RequestValidator requestValidator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(requestValidator);
}
#PostMapping(value = REDIRECT_MAPPING)
public ResponseEntity<ResponseDTO> redirectEndpoint(#Validated #RequestBody RequestDTO requestDTO, BindingResult result) {
if (result.hasErrors()) {
// Handle validation errors
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
// Do other stuff
return ResponseEntity.status(HttpStatus.OK).build();
}
}
And this test class:
#RunWith(SpringRunner.class)
#WebMvcTest(GatewayController.class)
public class GatewayControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private RequestValidator requestValidator;
#MockBean
private BindingResult bindingResult;
private JacksonTester<RequestDTO> requestJacksonTester;
#Before
public void setUp() throws Exception {
JacksonTester.initFields(this, new ObjectMapper());
Mockito.when(requestValidator.supports(ArgumentMatchers.any())).thenReturn(true);
}
#Test
public void whenRedirectWithValidationErrorsThenBadRequestReturned() throws Exception {
RequestDTO request = new RequestDTO();
// Set some values
Mockito.when(bindingResult.hasErrors()).thenReturn(true);
mockMvc.perform(MockMvcRequestBuilders.post(ROOT_MAPPING + REDIRECT_MAPPING)
.contentType(MediaType.APPLICATION_JSON)
.content(requestJacksonTester.write(request).getJson()))
.andExpect(MockMvcResultMatchers.status().isBadRequest());
}
}
When I run this code the test case fail with this reason: Status
Expected :400
Actual :200
So, what I want to do is to mock the BindingResult which passed as a parameter to the redirectEndpoint method in the Controller so that when calling bindingResult.hasErrors() this should return true and the test case pass.
I did many search but with no luck. Any suggestions how to do that?
Thanks in advance.
BindingResult is not a bean in the ApplicationContext. Thus, you cannot mock it via #MockBean.
A BindingResult is created for you by Spring MVC for each incoming HTTP request.
Thus, you don't want to mock the BindingResult. In fact, you probably don't want to mock the behavior of your RequestValidator either. Rather, you should ideally use the real implementation of your RequestValidator, pass in invalid request data (via MockMvc), and then verify the response accordingly.
Note that you should be able to include the real implementation of your RequestValidator via #Import(RequestValidator.class) on the test class.

Spring Boot Controller Test Failing

I have a RestController that has just one method for http GET and is taking no input arguments. It is calling the service method which takes some arguments. Below is the controller snippet.
#RequestMapping(value = "/leagueResults", method = RequestMethod.GET)
public List<LeagueTableEntry> getResults(){
List<LeagueTableEntry> leagueTableEntryList = new ArrayList<>();
List<Match> listOfMatches = getListOfMatches();
leagueTableEntryList = leagueService.getResults(listOfMatches);
return leagueTableEntryList;
}
Below is my ControllerTest class snippet
#RunWith(SpringRunner.class)
#WebMvcTest(LeagueController.class)
#WebAppConfiguration
public class LeagueControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private LeagueService leagueService ;
private List<LeagueTableEntry> leagueEntryList;
private List<Match> matchList;
#Before
public void setUp() throws Exception
{
MockitoAnnotations.initMocks(this);
createSampleInputData();//This method populates the instance variable matchList
getLeagueEntryOutput();//This method populates the instance variable leagueEntryList
}
#Test
public void checkNoOfRecordsReturned()throws Exception {
try{
Mockito.when(leagueService.getResults(matchList)).thenReturn(leagueEntryList);
mvc.perform(get("/leagueResults").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(4)));
}
catch(Exception e){
throw new Exception();
}
}
private void getLeagueEntryOutput(){
leagueEntryList = new ArrayList<>();
leagueEntryList.add(new LeagueTableEntry());
leagueEntryList.add(new LeagueTableEntry());
leagueEntryList.add(new LeagueTableEntry());
leagueEntryList.add(new LeagueTableEntry());
}
So, here I am expecting the count of objects in the returned list as 4, but it is coming as 0.So, my test is failing. Can you please let me know what am i missing.
I think you can instead of writing
Mockito.when(leagueService.getResults(matchList)).thenReturn(leagueEntryList);
write
Mockito.when(leagueService.getResults(Mockito.anyList())).thenReturn(leagueEntryList);
Also if this didn't work I would need to get the implementation of
List<Match> listOfMatches = getListOfMatches();

Can not attach body to my POST request using Spring MockMvc

I'm trying to test my rest controller. No issues with GETs, but when I try to test a POST method I'm unable to attach the body.
private static final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
private ObjectMapper jsonMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
#Test
public void test1() throws Exception {
//...Create DTO
//...Create same pojo but as entity
when(serviceMock.addEntity(e)).thenReturn(e);
mvc.perform(post("/uri")
.contentType(contentType)
.content(jsonMapper.writeValueAsString(dto))
)
.andDo(print())
.andExpect(status().isCreated())
.andExpect(content().contentType(contentType)); //fails because there is no content returned
}
This is the request output:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /uri
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8]}
There is no body. Why? I have printed jsonMapper.writeValueAsString(dto) and is not null.
edit:
Adding controller code:
#RestController
#RequestMapping("/companies")
public class CompanyController {
#Autowired
private CompanyService service;
#Autowired
private CompanyMapper mapper;
#RequestMapping(method=RequestMethod.GET)
public List<CompanyDTO> getCompanies() {
List<Company> result = service.getCompanies();
return mapper.toDtoL(result);
}
#RequestMapping(method=RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public CompanyDTO createCompany(#RequestBody #Valid CompanyDTO input) {
Company inputE = mapper.toEntity(input);
Company result = service.addCompany(inputE);
return mapper.toDto(result);
}
Solved.
The mock call should use any instead of a concrete object: when(serviceMock.addCompany(any(Company.class))).thenReturn(e);
I needed to override the equals method of the entity class to pass this statement: verify(serviceMock, times(1)).addCompany(e);

TDD test for Spring MVC controller not working correctly

I creating my first Spring MVC, Boot, Data web-app and I have a problem when I want to create tests. I just modify some example from Craig Wall book "Spring in Action".
I tried to write TDD test for my controller, for method registerNewAccount, I was sure everything was ok, but all time I get argumetns are different!. Full stack trace below:
Argument(s) are different! Wanted:
accountService.create(
com.conf.data.Account#2ed42a28
);
-> at com.conf.controller.AccountControllerSpec.registerNewUser(AccountControllerSpec.java:52)
Actual invocation has different arguments:
accountService.create(
com.conf.data.Account#2a4d1467
);
-> at com.conf.controller.AccountController.registerNewAccount(AccountController.java:32)
Argument(s) are different! Wanted:
accountService.create(
com.conf.data.Account#2ed42a28
);
-> at com.conf.controller.AccountControllerSpec.registerNewUser(AccountControllerSpec.java:52)
Actual invocation has different arguments:
accountService.create(
com.conf.data.Account#2a4d1467
);
-> at com.conf.controller.AccountController.registerNewAccount(AccountController.java:32)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
My Controller class:
#Controller
#RequestMapping(value = "/account")
public class AccountController {
private AccountService accountService;
#Autowired
public AccountController(AccountService accountService){
this.accountService = accountService;
}
#RequestMapping(value = "/register", method = RequestMethod.GET)
public String showRegistrationForm(){
return "account/register";
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String registerNewAccount(Account account){
accountService.create(account);
return "redirect:/";
}
}
My Service class:
#Service
#Transactional
public class AccountService {
private AccountRepository repository;
#Autowired
public AccountService(AccountRepository repository) {
this.repository = repository;
}
public Account create(Account account){
return repository.save(account);
}
}
My repository Class:
#Component
public interface AccountRepository extends CrudRepository<Account, Long>{
}
And my Spec Class:
public class AccountControllerSpec {
private MockMvc mockMvc;
private AccountService service;
private AccountController controller;
#Before
public void before(){
service = mock(AccountService.class);
controller = new AccountController(service);
mockMvc = standaloneSetup(controller).build();
}
#Test
public void registrationPage() throws Exception {
mockMvc.perform(get("/account/register")).andExpect(view().name("account/register"));
}
#Test
public void registerNewAccount() throws Exception {
Account unsaved = new Account("M","B","m#gmail.com");
Account saved = new Account("M","B","m#gmail.com");
when(service.create(unsaved)).thenReturn(saved);
mockMvc.perform(post("/account/register")
.param("firstName","M")
.param("lastName","B")
.param("email","m#gmail.com"))
.andExpect(redirectedUrl("/"));
verify(service,atLeastOnce()).create(unsaved);
}
}
What am I doing wrong?
You expect our controller to call your service with the unsaved account. But when you invoke your controller, Spring creates a new Account instance from the parameters sent in the request, and invokes the service with that Account, which is not equal to unsaved.
So, either define equals() and hashCode(), so that the actual instance created by Spring is equal to unsaved, or use another matcher, which accepts any Account, or use an ArgumentCaptor to capture the actual Account passed by your service, and check that it has the expected properties.

Resources