Hot questions for Using Mockito in controller

Hot questions for Using Mockito in controller

Question:

While writing unit tests postmortem to code that another project created, I came across this issue of how to mock a validator that is bound to the controller with initBinder?

Normally I would just consider making sure my inputs are valid and be done with a few extra calls in the validator, but in this case the validator class is coupled with doing checks through a few data sources and it all becomes quite a mess to test. Coupling dates back to some old common libraries used and is outside the scope of my current work to fix all of them.

At first I tried to just mock out the external dependencies of the validator using PowerMock and mocking static methods, but eventually ran into a class that requires a data source when the class is created and didn't find a way around that one.

Then I tried to just use normal mockito tools to mock out the validator, but that didn't work either. Then tried to set the validator in the mockMvc call, but that doesn't register any more than a @Mock annotation for the validator. Finally ran into this question. But since there's no field validator on the controller itself, this fails too. So, how can I fix this to work?

Validator:

public class TerminationValidator implements Validator {
    // JSR-303 Bean Validator utility which converts ConstraintViolations to Spring's BindingResult
    private CustomValidatorBean validator = new CustomValidatorBean();

    private Class<? extends Default> level;

    public TerminationValidator(Class<? extends Default> level) {
        this.level = level;
        validator.afterPropertiesSet();
    }

    public boolean supports(Class<?> clazz) {
        return Termination.class.equals(clazz);
    }

    @Override
    public void validate(Object model, Errors errors) {
        BindingResult result = (BindingResult) errors;

        // Check domain object against JSR-303 validation constraints
        validator.validate(result.getTarget(), result, this.level);

        [...]
    }

    [...]
}

Controller:

public class TerminationController extends AbstractController {

    @InitBinder("termination")
    public void initBinder(WebDataBinder binder, HttpServletRequest request) {
        binder.setValidator(new TerminationValidator(Default.class));
        binder.setAllowedFields(new String[] { "termId[**]", "terminationDate",
                "accountSelection", "iban", "bic" });
    }

    [...]
}

Test class:

@RunWith(MockitoJUnitRunner.class)
public class StandaloneTerminationTests extends BaseControllerTest {
    @Mock
    private TerminationValidator terminationValidator = new TerminationValidator(Default.class);

    @InjectMocks
    private TerminationController controller;

    private MockMvc mockMvc;

    @Override
    @Before
    public void setUp() throws Exception {
        initMocks(this);

        mockMvc = standaloneSetup(controller)
                      .setCustomArgumentResolvers(new TestHandlerMethodArgumentResolver())
                      .setValidator(terminationValidator)
                      .build();

        ReflectionTestUtils.setField(controller, "validator", terminationValidator);

        when(terminationValidator.supports(any(Class.class))).thenReturn(true);
        doNothing().when(terminationValidator).validate(any(), any(Errors.class));
    }

    [...]
}

Exception:

java.lang.IllegalArgumentException: Could not find field [validator] of type [null] on target [my.application.web.controller.TerminationController@560508be]
    at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:111)
    at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:84)
    at my.application.web.controller.termination.StandaloneTerminationTests.setUp(StandaloneTerminationTests.java:70)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

Answer:

You should avoid creating business objects with new in a Spring application. You should always get them from the application context - it will ease mocking them in your test.

In your use case, you should simply create your validator as a bean (say defaultTerminationValidator) and inject it in your controller :

public class TerminationController extends AbstractController {

    private TerminationValidator terminationValidator;

    @Autowired
    public setDefaultTerminationValidator(TerminationValidator validator) {
        this.terminationValidator = validator;
    }

    @InitBinder("termination")
    public void initBinder(WebDataBinder binder, HttpServletRequest request) {
        binder.setValidator(terminationValidator);
        binder.setAllowedFields(new String[] { "termId[**]", "terminationDate",
                "accountSelection", "iban", "bic" });
    }

    [...]
}

That way, you will be able to simply inject a mock in your test.

Question:

I want to write unit tests for my spring controller. I'm using keycloak's openid flow to secure my endpoints.

In my tests I'm using the @WithMockUser annotation to mock an authenticated user. My problem is that I'm reading the userId from the token of the principal. My unit test now fails because the userId I read from the token is null;

        if (principal instanceof KeycloakAuthenticationToken) {
            KeycloakAuthenticationToken authenticationToken = (KeycloakAuthenticationToken) principal;
            SimpleKeycloakAccount account = (SimpleKeycloakAccount) authenticationToken.getDetails();
            RefreshableKeycloakSecurityContext keycloakSecurityContext = account.getKeycloakSecurityContext();
            AccessToken token = keycloakSecurityContext.getToken();
            Map<String, Object> otherClaims = token.getOtherClaims();
            userId = otherClaims.get("userId").toString();
        }

Is there anything to easily mock the KeycloakAuthenticationToken?


Answer:

I was able to test after adding the following things:

  1. Add some fields:

    @Autowired
    
        private WebApplicationContext context:
        private MockMvc mockMvc;
    
  2. In my @Before setup() method:

    mockMvc = MockMvcBuilders.webAppContextSetup(context)
       .alwaysDo(print())
       .apply(springSecurity())
       .build(); 
    
  3. Inside my test method:

    SecurityContext context = SecurityContextHolder.getContext();
    Authentication authentication = context.getAuthentication();
    

When I pass the authentication object to the method where I read the claims from the keycloak token I can run the test without any problems.

P.S. Don't forget the @WithMockUser annotation

Question:

Parameters with @RequestParam annotation can be passed by using : post("/******/***").param("variable", "value")

but how can I pass value of parameter having @RequestBody annotation?

My test method is :

@Test
    public void testCreateCloudCredential() throws Exception {

        CloudCredentialsBean cloudCredentialsBean = new CloudCredentialsBean();
        cloudCredentialsBean.setCloudType("cloudstack");
        cloudCredentialsBean.setEndPoint("cloudstackendPoint");
        cloudCredentialsBean.setUserName("cloudstackuserName");
        cloudCredentialsBean.setPassword("cloudstackpassword");
        cloudCredentialsBean.setProviderCredential("cloudstackproviderCredential");
        cloudCredentialsBean.setProviderIdentity("cloudstackproviderIdentity");
        cloudCredentialsBean.setProviderName("cloudstackproviderName");
        cloudCredentialsBean.setTenantId(78);
        cloudCredentialsBean.setCredentialId(98);

        StatusBean statusBean = new StatusBean();
        statusBean.setCode(200);
        statusBean.setStatus(Constants.SUCCESS);
        statusBean.setMessage("Credential Created Successfully");

        Gson gson = new Gson();
        String json = gson.toJson(cloudCredentialsBean);
        ArgumentCaptor<String> getArgumentCaptor =
            ArgumentCaptor.forClass(String.class);
        ArgumentCaptor<Integer> getInteger = ArgumentCaptor.forClass(Integer.class);

        ArgumentCaptor<CloudCredentialsBean> getArgumentCaptorCredential =
            ArgumentCaptor.forClass(CloudCredentialsBean.class);
        when(
            userManagementHelper.createCloudCredential(getInteger.capture(),
                    getArgumentCaptorCredential.capture())).thenReturn(
            new ResponseEntity<StatusBean>(statusBean, new HttpHeaders(),
                HttpStatus.OK));

        mockMvc.perform(
            post("/usermgmt/createCloudCredential").param("username", "aricloud_admin").contentType(
                MediaType.APPLICATION_JSON).content(json)).andExpect(
            status().isOk());

    }

Controller method that is being tested is :

@RequestMapping(value = "/createCloudCredential", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<StatusBean> createCloudCredential(
            @RequestParam("userId") int userId,
         @RequestBody CloudCredentialsBean credential) {            
            return userManagementHepler.createCloudCredential(userId, credential);
        }   

Error that I am getting is : How can I pass a mock value for credential here?


Answer:

A POST request normally passes its param in its body. So I cannot understand what you expect by giving both a param and a content for same request.

So here, you can simply do:

    mockMvc.perform(
        post("/usermgmt/createCloudCredential").contentType(
            MediaType.APPLICATION_JSON).content(json)).andExpect(
        status().isOk());

If you need to pass the parameter "username=aricloud_admin", add it to the JSON string, or alternatively, pass it explicitly as a query string:

    mockMvc.perform(
        post("/usermgmt/createCloudCredential?username=aricloud_admin")
            .contentType(MediaType.APPLICATION_JSON).content(json))
            .andExpect(status().isOk());

Question:

I need to test my controller methods including a delete method. Here is partial controller code:

@RestController
@RequestMapping("/api/foo")
public class FooController {

    @Autowired
    private FooService fooService;

    // other methods which works fine in tests

    @RequestMapping(path="/{id}", method = RequestMethod.DELETE)
    public void delete(@PathVariable Long id) {
        fooService.delete(id);
    }    
}

And here is my test:

@InjectMocks
private FooController fooController;

@Before
public void setUp() {
    this.mockMvc = MockMvcBuilders.standaloneSetup(fooController)
.setControllerAdvice(new ExceptionHandler()).alwaysExpect(MockMvcResultMatchers.content().contentType("application/json;charset=UTF-8")).build();
}

@Test
public void testFooDelete() throws Exception {
    this.mockMvc.perform(MockMvcRequestBuilders
            .delete("/api/foo")
            .param("id", "11")
            .contentType(MediaType.APPLICATION_JSON))
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk());
}

Test finish with failure because incorrect status code:

java.lang.AssertionError: Status Expected :200 Actual :400

In console log I also found this:

2017-12-11 20:11:01 [main] DEBUG o.s.t.w.s.TestDispatcherServlet - DispatcherServlet with name '' processing DELETE request for [/api/foo]
2017-12-11 20:11:01 [main] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Looking up handler method for path /api/foo
2017-12-11 20:11:01 [main] DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolving exception from handler [null]: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'DELETE' not supported
2017-12-11 20:11:01 [main] DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Invoking @ExceptionHandler method: public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> cz.ita.javaee.web.controller.error.ExceptionHandler.handleException(java.lang.Exception)
2017-12-11 20:11:01 [main] DEBUG o.s.w.s.m.m.a.HttpEntityMethodProcessor - Written [{stackTrace=org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'DELETE' not supported

Can you tell me how to fix it? Thanks.


Answer:

Your target URI is: /api/foo/11 based on this root: /api/foo and this path variable: /{id}.

When using MockMvc you set path variables (aka URI variables) like so:

delete(uri, uriVars)

More details in the Javadocs.

So, your test should read:

@Test
public void testFooDelete() throws Exception {
    this.mockMvc.perform(MockMvcRequestBuilders
            .delete("/api/foo/{id}", "11")
            .contentType(MediaType.APPLICATION_JSON))
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk());
}

Or alternatively:

@Test
public void testFooDelete() throws Exception {
    this.mockMvc.perform(MockMvcRequestBuilders
            .delete("/api/foo/11")
            .contentType(MediaType.APPLICATION_JSON))
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk());
}

Question:

I have added the setHandlerExceptionResovlers to my builder and that works well except the exceptionhandler bean has autowired MessageSource and when unit testing this is null. I assume it might be cause of having to manually setup the exception handler in test. The flow is calling the controller with invalid parm, the controller calls the exception handler with a BindException.

   @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = AutoControllerTest.TestContextConfiguration.class)
    @WebAppConfiguration
    public class AutoControllerTest {
        @Mock
        private AutoFacade policyFacade;

        @Mock
        private MessageSource messageSource;

        @Mock
        private RestExceptionProcessor processor;

        @InjectMocks
        private AutoController policyController;

        private MockMvc mockMvc;

        String validPolicyNbr;
        String tooLongPolicyNbr;
        AutoPolicy autoPolicy;

        @Before
        public void setUp() {
            MockitoAnnotations.initMocks(this);

            this.mockMvc = MockMvcBuilders.standaloneSetup(policyController)
                    .setHandlerExceptionResolvers(createExceptionResolver())
                    .build();


            tooLongPolicyNbr = new String("000000000000000000000000");
            validPolicyNbr = new String("4239326");
            autoPolicy = new AutoPolicy();
        }

        private ExceptionHandlerExceptionResolver createExceptionResolver() {
            ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
                protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
                    Method method = new ExceptionHandlerMethodResolver(RestExceptionProcessor.class).resolveMethod(exception);
              // Changed     return new ServletInvocableHandlerMethod(new 
    //RestExceptionProcessor(), method);
    //Changed to resolve question 
       return new ServletInvocableHandlerMethod(new RestExceptionProcessor(messageSource), method);
                }
            };
    //Added to resolve question 
                 exceptionResolver.getMessageConverters().add(
                    new MappingJackson2HttpMessageConverter());
    //Finish
            exceptionResolver.afterPropertiesSet();
            return exceptionResolver;
        }



        @Test
        public void getPolicyDetails_TooLongPolicyNbr_ReturnsDetails() throws Exception {
                 /** Removed **/
               // MessageSource ms = messageSource;       
               // processor.setMessageSource(ms);

            this.mockMvc.perform(get("/policies/policy/{policyNbr}", tooLongPolicyNbr))
                .andDo(print())
                .andExpect(status().isOk());
        }

        @EnableAutoConfiguration
        @ComponentScan(basePackages = { "com.api" })
        @Configuration
        static public class TestContextConfiguration {
            //Removed   
            //@Bean
            //public MessageSource messageSource() {
                //return Mockito.mock(MessageSource.class);
            //}
            //Removed End
      //Added

        @Bean
    public MessageSource messageSource() {

        final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasenames( "classpath:messages"  );
    messageSource.setDefaultEncoding( "UTF-8" );
    messageSource.setCacheSeconds( 60 );
    messageSource.setFallbackToSystemLocale( false );
    return messageSource;           
    }
        //Added end
            @Bean
            public AutoFacade policyFacade() {
                return Mockito.mock(AutoFacade.class);
            }

            @Bean
            public HttpServletRequest request() {
                return Mockito.mock(HttpServletRequest.class);
            }

            @Bean
            public RestExceptionProcessor processor() {
                return Mockito.mock(RestExceptionProcessor.class);
            }

        }
    }




     @ControllerAdvice
        public class RestExceptionProcessor {
            private static final Logger logger = LoggerFactory.getLogger(RestExceptionProcessor.class);

            @Autowired
            private MessageSource messageSource;

            //Removed
            //public void setMessageSource(MessageSource ms){
              //      this.messageSource = ms;
            //    }
            //Added
                public RestExceptionProcessor(MessageSource messageSource) {
        super();
        this.messageSource = messageSource;
        }

        public RestExceptionProcessor() {
        super();
          }



        @ExceptionHandler({BindException.class})
            @ResponseStatus(value=HttpStatus.BAD_REQUEST)
            @ResponseBody
            public APIStatus validationError(BindException ex) {

                int statusCd = Integer.parseInt(HttpStatus.BAD_REQUEST.toString());
                String statusMessage = messageSource.getMessage("bad.request.status.msg.400", null, Locale.US);

                String errorURL = null;
                int errorCode =  400000;
                String errorType = messageSource.getMessage("bad.request.error.type.400", null, Locale.US);
                List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();      
                List<com.fbitn.api.errors.FieldError> apiFieldErrors = new ArrayList<com.fbitn.api.errors.FieldError>();

                for (FieldError fieldError: fieldErrors) {
                    String errorMessage =  messageSource.getMessage(fieldError, Locale.US);
                    com.fbitn.api.errors.FieldError apiFieldError = new com.fbitn.api.errors.FieldError(fieldError.getField(), errorMessage);
                    apiFieldErrors.add(apiFieldError);
                }
                APIError apiError = new APIError(errorCode, errorType, "Validation Error", apiFieldErrors);
                List<APIError> errorInfoList = new ArrayList<APIError>();
                errorInfoList.add(apiError);    

                return new APIStatus(statusCd, errorURL, statusMessage, errorInfoList);
            }   

Answer:

I had to manually register the MessageSource in my test configs and create a constructor in the ControllerAdvice to add parm MeasageSource. Also had an issue with MessageConverter not being found for my JSON response, had to set the convert in ExceptionHandlerExceptionResolver. Edited my question to show the reflected changes.

Question:

I have Rest Controller with the method create(validation using util class + databaseService(databaseDao + caching))

@RestController
@RequestMapping("files")
public class FilesController {
    private IDbFilesDao dbFilesService;
    private Map<String, Table> tables;

    public FilesController(IDbFilesDao dbFilesService, Map<String, Table> tables) {
        this.dbFilesService = dbFilesService;
        this.tables = tables;
    }

    @PostMapping("{table}")
    public ResponseEntity createTable(@PathVariable("table") String tableName,
                                         @RequestBody File file) {
        FilesValidator.validateAdding(tableName, tables, file);

        dbFilesService.create(tableName, file);

        URI location = ServletUriComponentsBuilder.fromCurrentRequest().buildAndExpand(file.getKey()).toUri();
        return ResponseEntity.created(location).build();
    }
}

I have a Test:

@RunWith(SpringRunner.class)
@WebMvcTest(value = FilesController.class, secure = false)
public class FilesControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private IDbFilesDao dbFilesService;

    @MockBean
    private Map<String, Table> tables;

    @Test
    public void create() throws Exception {
        RequestBuilder requestBuilder = MockMvcRequestBuilders
                .post("/files/tableName")
                .accept(MediaType.APPLICATION_JSON)
                .content(POST_JSON_BODY)
                .contentType(MediaType.APPLICATION_JSON);
        MvcResult result = mockMvc.perform(requestBuilder).andReturn();
        MockHttpServletResponse response = result.getResponse();
        assertEquals(HttpStatus.CREATED.value(), response.getStatus());
    }
}

It works well only without this row in @RestContoller:

FilesValidator.validateAdding(tableName, tables, file);

With this row - 404 not found.

FilesValidator - util class with static methods. It checks if data is valid and do nothing or throw a Runtime Exception with status code ( 404 for example).

How can I fix it without deliting Validation?


Answer:

1) Move the validator call to a package level method and do small refactoring:

@PostMapping("{table}")
    public ResponseEntity createTable(@PathVariable("table") String tableName,
                                         @RequestBody File file) {
        validateAdding(tableName, tables, file);
        ...
}

validateAdding(String tableName, Map<String, Table> tables, File file){
    FilesValidator.validateAdding(tableName, tables, file);
}

2) Spy the controller in the test:

@SpyBean
private FilesController filesControllerSpy;

3) Make validateAdding method do nothing:

@Test
public void create() throws Exception {

   doNothing().when(filesControllerSpy)
     .validateAdding(any(String.class), any(Map.class), any(File.class));
   ...

Question:

I'm testing my Spring MVC controllers using JUnit, Mockito & spring-test. Unfortunately, the tests are ignoring the @PreAuthorize annotations on my controller methods, and I've been unable to resolve this. Key code snippets are below, though I've removed irrelevant logic for mocking responses from MyController's dependencies to keep it short. I'm using Spring 3.2 & JUnit 4, and I'm running the tests both via Maven and directly through Eclipse (Run as -> JUnit test).

Right now I am expecting the getAccounts_ReturnsOkStatus test to fail, as I have not provided any auth and the method that the /accounts route maps onto is annotated with a pre-auth annotation, however the method is being invoked and the pre-auth check bypassed.

MyControllerTest.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext-test.xml" })
public class MyControllerTest {

    private MockMvc mockMvc;

    @Mock
    private MyService myService;

    @InjectMocks
    private MyController myController;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);  
        mockMvc = MockMvcBuilders.standaloneSetup(myController).build();
    }

   @Test
   public void getAccounts_ReturnsOkStatus() throws Exception {
       // Make the GET request, and verify that it returns HttpStatus.OK (200)
       mockMvc.perform(MockMvcRequestBuilders.get("/accounts"))
              .andExpect(MockMvcResultMatchers.status().isOk());
   }
}

applicationContext-test.xml

<sec:global-method-security pre-post-annotations="enabled" />
<bean id="applicationContextProvider" class="com.myapp.springsupport.ApplicationContextProvider" />

<sec:authentication-manager alias="authenticationManager">
   <sec:authentication-provider>
       <sec:user-service>
           <sec:user name="test-superuser" password="test-pwd" authorities="ROLE_SUPERUSER" />
       </sec:user-service>
   </sec:authentication-provider>
</sec:authentication-manager>

MyController.java

@PreAuthorize("isAuthenticated() and hasRole('ROLE_SUPERUSER')")
@RequestMapping(value = "/accounts", method = RequestMethod.GET)
@ResponseBody
public Collection<Account> getAccounts() {
    return new ArrayList<Account>();
}

The applicationContext-test app context is definitely being used, since manually authenticating using

Authentication auth = new UsernamePasswordAuthenticationToken(name, password);
SecurityContextHolder.getContext().setAuthentication(am.authenticate(auth));

only works with the user credentials specified in the test config (in the absence of any other authentication-provider). Additionally, I can be sure that the pre-auth is being ignored, since I've tested using SEL to invoke a method & debugged.

What am I missing?


Answer:

You have to enable spring security for testing when building mockMvc.

In Spring 4 it's like this:

mockMvc = MockMvcBuilders.webAppContextSetup(context)
                         .apply(springSecurity())
                         .build();

In Spring 3 it's like this:

@Autowired
private Filter springSecurityFilterChain;
...
mockMvc = MockMvcBuilders.webAppContextSetup(context)
                         .addFilters(springSecurityFilterChain)
                         .build();

For more details see:

Question:

I have a spring-boot application which exposes a REST interface via a controller. This is an example of my controller:

@RestController
public class Controller {

  @Autowired
  private Processor processor;

  @RequestMapping("/magic")
  public void handleRequest() {

    // process the POST request
    processor.process();

  }   
}

I am trying to write unit tests for this class and I have to mock the processor (since the processing takes very long time and I am trying to avoid this step during testing the controller behavior). Please note, that the provided example is simplified for the sake of this question.

I am trying to use the mockito framework for this task:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = App.class)
@WebAppConfiguration
@ActiveProfiles("test")
public class ControllerTest {

  @Autowired
  private WebApplicationContext wac;

  private MockMvc mockMvc;

  @Before
  public void setUp() throws Exception {

    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();

    Processor processor = Mockito.mock(Processor.class);
    ReflectionTestUtils.setField(Controller.class, "processor", processor);
  }

  @Test
  public void testControllerEmptyBody() throws Exception {

    this.mockMvc.perform(post("/magic")).andExpect(status().isOk());

  }
}

However, this fails with

java.lang.IllegalArgumentException: Could not find field [processor] of type [null] on target [class org.company.Controller]
    at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:112)
    ...

Could please someone give me a hint, how this mock could be injected in my controller?


Answer:

Shouldn't you be passing an instance to set the field on, rather than the class, e.g.:

...

@Autowired
private Controller controller;

...

@Before
public void setUp() throws Exception {

    ...

    Processor processor = Mockito.mock(Processor.class);
    ReflectionTestUtils.setField(controller, "processor", processor);
}

Question:

I am facing a problem to test a controller with pagination for my Spring boot MVC web project which uses Thymeleaf . My controller is as follows:

@RequestMapping(value = "admin/addList", method = RequestMethod.GET)
    public String druglist(Model model, Pageable pageable) {

        model.addAttribute("content", new ContentSearchForm());
        Page<Content> results = contentRepository.findContentByContentTypeOrByHeaderOrderByInsertDateDesc(
                ContentType.Advertisement.name(), null, pageable);


        PageWrapper<Content> page = new PageWrapper<Content>(results, "/admin/addList");
        model.addAttribute("contents", results);
        model.addAttribute("page", page);
        return "contents/addcontents";

    }

I have tried with this following test segment which will count the contents items (initially it will return 0 item with pagination).

andExpect(view().name("contents/addcontents"))
    .andExpect(model().attributeExists("contents"))
    .andExpect(model().attribute("contents", hasSize(0)));

but getting this following error ( test was fine, before pagination):

 java.lang.AssertionError: Model attribute 'contents'
Expected: a collection with size <0>
     but: was <Page 0 of 0 containing UNKNOWN instances>
 at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)

I have goggled but no luck here. Can anybody help me with an example to test a controller which deals with pageable object from repository?

Is there any alternate way exist to test list with pagination? Please help.

Thanks in advance!


Answer:

your testing the attribute contents. contents is of type Page as you added it under that name into the model (model.addAttribute("contents", results);) Page has no attribute size, it isn't a list.

You want to check for the total number of elements instead:

.andExpect(view().name("contents/addcontents"))
.andExpect(model().attributeExists("contents"))
.andExpect(model().attribute("contents", Matchers.hasProperty("totalElements", equalTo(0L))));

I have included the Hamcrest utility classes for your convenience. Usually I omit them like here https://github.com/EuregJUG-Maas-Rhine/site/blob/ea5fb0ca6e6bc9b8162d5e83a07e32d6fc39d793/src/test/java/eu/euregjug/site/web/IndexControllerTest.java#L172-L191

Question:

I write a Spring Boot app and I was able to access and test Controller with MockMvc. The issue is that during testing security is not enforced and I can access Controller with no user.

Am I doing anything wrong? Is it intended behavior?

ControllerTest class:

@RunWith(MockitoJUnitRunner.class)
public class ControllerTest {

    private MockMvc mockMvc;

    @Mock
    private Service service;

    @InjectMocks
    private Controller controller;

    private final static String URL = "/test";

    @Before
    public void setUp() throws Exception {
        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    }

    @Test
    public void test() throws Exception {
        mockMvc.perform(get(URL))
        .andExpect(status().isOk());
    }

}

My SecurityConfig StackOverflow QA.


Answer:

Your examples uses a plain unit test to test your controller. In this setup the Controller is created by Mockito (the controller field is annotated with Mockito's @InjectMocks).

Mockito is not aware of Spring, in consequence no Spring Security will be setup in your test.

You need to use the SpringRunner to run your test. This runner is Spring aware and allows you to properly initialize your controller before the test is run.

The test should look something like this (junit5):

@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = Controller.class)
public class ControllerTest {
  @Autowired
  private MockMvc mockMvc;

  @MockBean
  private Service serviceMock;

   @Test
    public void test() throws Exception {
        mockMvc.perform(get(URL))
        .andExpect(status().isOk());
    }

}

check our the Spring documentation or some tutorials for further information

Question:

I am big on clean well-isolated unit tests. But I am stumbling on the "clean" part here for testings a controller that uses DomainClassConverter feature to get entities as parameters for its mapped methods.

@Entity
class MyEntity {
    @Id
    private Integer id;
    // rest of properties goes here.
}

The controller is defined like this

@RequestMapping("/api/v1/myentities")
class MyEntitiesController {
    @Autowired
    private DoSomethingService aService;

    @PostMapping("/{id}")
    public ResponseEntity<MyEntity> update(@PathVariable("id")Optional<MyEntity> myEntity) {
        // do what is needed here
    }
}

So from the DomainClassConverter small documentation I know that it uses CrudRepository#findById to find entities. What I would like to know is how can I mock that cleanly in a test. I have had some success by doing this steps:

  1. Create a custom Converter/Formatter that I can mock
  2. Instantiate my own MockMvc with above converter
  3. reset mock and change behaviour at each test.

The problem is that the setup code is complex and thus hard to debug and explain (my team is 99% junior guys coming from rails or uni so we have to keep things simple). I was wondering if there is a way to inject the desired MyEntity instances from my unit test while keep on testing using the @Autowired MockMvc.

Currently I am trying to see if I can inject a mock of the CrudRepository for MyEntity but no success. I have not worked in Spring/Java in a few years (4) so my knowledge of the tools available might not be up to date.


Answer:

So from the DomainClassConverter small documentation I know that it uses CrudRepository#findById to find entities. What I would like to know is how can I mock that cleanly in a test.

You will need to mock 2 methods that are called prior the CrudRepository#findById in order to return the entity you want. The example below is using RestAssuredMockMvc, but you can do the same thing with MockMvc if you inject the WebApplicationContext as well.

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SomeApplication.class)
public class SomeControllerTest {

    @Autowired
    private WebApplicationContext context;

    @MockBean(name = "mvcConversionService")
    private WebConversionService webConversionService;

    @Before
    public void setup() {
        RestAssuredMockMvc.webAppContextSetup(context);

        SomeEntity someEntity = new SomeEntity();

        when(webConversionService.canConvert(any(TypeDescriptor.class), any(TypeDescriptor.class)))
                .thenReturn(true);

        when(webConversionService.convert(eq("1"), any(TypeDescriptor.class), any(TypeDescriptor.class)))
                .thenReturn(someEntity);
    }
}

At some point Spring Boot will execute the WebConversionService::convert, which will later call DomainClassConverter::convert and then something like invoker.invokeFindById, which will use the entity repository to find the entity.

So why mock WebConversionService instead of DomainClassConverter? Because DomainClassConverter is instantiated during application startup without injection:

DomainClassConverter<FormattingConversionService> converter =
        new DomainClassConverter<>(conversionService);

Meanwhile, WebConversionService is a bean which will allow us to mock it:

@Bean
@Override
public FormattingConversionService mvcConversionService() {
    WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
    addFormatters(conversionService);
    return conversionService;
}

It is important to name the mock bean as mvcConversionService, otherwise it won't replace the original bean.

Regarding the stubs, you will need to mock 2 methods. First you must tell that your mock can convert anything:

when(webConversionService.canConvert(any(TypeDescriptor.class), any(TypeDescriptor.class)))
        .thenReturn(true);

And then the main method, which will match the desired entity ID defined in the URL path:

when(webConversionService.convert(eq("1"), any(TypeDescriptor.class), any(TypeDescriptor.class)))
        .thenReturn(someEntity);

So far so good. But wouldn't be better to match the destination type as well? Something like eq(TypeDescriptor.valueOf(SomeEntity.class))? It would, but this creates a new instance of a TypeDescriptor, which will not match when this stub is called during the domain conversion.

This was the cleanest solution I've put to work, but I know that it could be a lot better if Spring would allow it.

Question:

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 ?


Answer:

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

Question:

Expecting the controller method to return the newly created weather resource, but the response body is empty.

Mocked the service to return a weather resource when the service method is called.

POST method for weather resource:

    @ApiOperation("Creates a new weather data point.")
    public ResponseEntity<Weather> createWeather(@Valid @RequestBody Weather weather) {     
        try {
            Weather createdWeather = weatherService.createWeather(weather);

            return ResponseEntity.ok(createdWeather);
        } catch(SQLException e) {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

Test:

    @Test
    public void createWeather_200() throws Exception {
        Weather weather = new Weather(null, "AC", new Date(1560402514799l), 15f, 10, 2);
        Weather createdWeather = new Weather(1, "AC", new Date(1560402514799l), 15f, 10, 2);

        given(service.createWeather(weather)).willReturn(createdWeather);

        MvcResult result = mvc.perform(post("/weather")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(weather)))
        .andExpect(status().isOk())
                .andExpect(jsonPath("$['id']", is(createdWeather.getId())));

    }

The tests are working for GET and DELETE methods. Could it be that the given weather object in the test doesn't match the actual object created in the controller?


Answer:

You are telling Mockito that you are expecting the exact weather object as an input.

When you call mvc though the object is converted to JSON and then parsed and finally passed to Service as a different instance than you pass to Mockito.

A solution is to use a wildcard as follows:

given(service.createWeather(Mockito.any(Weather.class))).willReturn(createdWeather);

Question:

I'm new to Spring Boot and Testing.

tl;dr How do I replace a @MockBean controller with the actual controller, in a spring boot application so that I can test that the controller is working instead of just testing that my objects are output correctly?

I'm writing a gradle managed API with dependencies (from build.gradle):

// Spring Boot (2.0.5 Release)
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-hateoas')
compile('org.springframework.boot:spring-boot-starter-web')
runtime('org.springframework.boot:spring-boot-devtools')

// Testing
testImplementation('org.junit.jupiter:junit-jupiter-api:5.3.1')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.3.1')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile("org.assertj:assertj-core:3.11.1")
testCompile 'org.mockito:mockito-core:2.+'

I've got an API controller class with the following relevant code:

@Controller
public class ObjectivesApiController extends AbstractRestHelperFunctionality implements ObjectivesApi {

protected ObjectivesApiController(
        UserRepository userRepository,
        CompaniesRepository companiesRepository,
        TeamsRepository teamsRepository,
        ProjectsRepository projectsRepository,
        OdiAssessmentRepository odiAssessmentRepository,
        OdiCustomerRatingRepository odiCustomerRatingRepository,
        OdiTechRatingRepository odiTechRatingRepository,
        OdiValueRatingRepository odiValueRatingRepository,
        ObjectivesRepository objectivesRepository,
        KeyResultRepository keyResultRepository) {
    super(
            userRepository,
            companiesRepository,
            teamsRepository,
            projectsRepository,
            odiAssessmentRepository,
            odiCustomerRatingRepository,
            odiTechRatingRepository,
            odiValueRatingRepository,
            objectivesRepository,
            keyResultRepository);
}

public ResponseEntity<KeyResult> createKeyResult(@ApiParam(value = "id", required = true) @PathVariable("id") Long id, @ApiParam(value = "keyResult", required = true) @Valid @RequestBody KeyResult keyResultDTO) {

    KeyResult keyResult = KeyResultBuilder
            .aKeyResult()
            .withDescription(keyResultDTO.getDescription())
            .withCompleted(keyResultDTO.getCompleted())
            .build();

    Objective parentObjective = objectivesRepository.findByObjectiveId(id);
    parentObjective.addKeyResult(keyResult);
    keyResultRepository.save(keyResult);
    objectivesRepository.save(parentObjective);

    return new ResponseEntity<KeyResult>(HttpStatus.CREATED);
}

public ResponseEntity<Objective> createObjective(@ApiParam(value = "objective", required = true) @Valid @RequestBody Objective objectiveDTO) {

    Objective objective = ObjectiveBuilder
            .anObjective()
            .withDescription(objectiveDTO.getDescription())
            .withCompleted(objectiveDTO.getCompleted())
            .withKeyResults(objectiveDTO.getKeyResults())
            .build();

    objective.getKeyResults().forEach(keyResultRepository::save);

    objectivesRepository.save(objective);
    return new ResponseEntity<Objective>(HttpStatus.CREATED);
}

public ResponseEntity<Void> deleteAllLinkedKeyResults(@ApiParam(value = "id", required = true) @PathVariable("id") Long id) {
    Objective subjectObjective = objectivesRepository.findByObjectiveId(id);

    subjectObjective.getKeyResults().clear();
    objectivesRepository.save(subjectObjective);

    return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}

public ResponseEntity<Void> deleteObjective(@ApiParam(value = "id", required = true) @PathVariable("id") Long id) {
    objectivesRepository.delete(objectivesRepository.findByObjectiveId(id));
    return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}

public ResponseEntity<Void> deleteOneKeyResult(@ApiParam(value = "the id of the objective you want key results for", required = true) @PathVariable("objectiveId") Long objectiveId, @ApiParam(value = "the id of the key result", required = true) @PathVariable("keyResultId") Long keyResultId) {
    Objective subjectObjective = objectivesRepository.findByObjectiveId(objectiveId);
    KeyResult keyResult = keyResultRepository.findByKeyResultId(keyResultId);

    subjectObjective.removeKeyResult(keyResult);

    objectivesRepository.save(subjectObjective);
    keyResultRepository.delete(keyResult);

    return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}

public ResponseEntity<List<Objective>> getAllObjectives() {
    List<Objective> allObjectives = objectivesRepository.findAll();
    return new ResponseEntity<List<Objective>>(allObjectives, HttpStatus.OK);
}

public ResponseEntity<List<KeyResult>> getKeyResultsForObjective(@ApiParam(value = "id", required = true) @PathVariable("id") Long id) {
    Objective subjectObjective = objectivesRepository.findByObjectiveId(id);
    List<KeyResult> allKeyResults = subjectObjective.getKeyResults();
    return new ResponseEntity<List<KeyResult>>(allKeyResults, HttpStatus.OK);
}

public ResponseEntity<Objective> getObjective(@ApiParam(value = "id", required = true) @PathVariable("id") Long id) {
    Objective subjectObjective = objectivesRepository.findByObjectiveId(id);
    return new ResponseEntity<Objective>(subjectObjective, HttpStatus.OK);
}

public ResponseEntity<KeyResult> getKeyResultForObjective(@ApiParam(value = "the id of the objective you want key results for", required = true) @PathVariable("objectiveId") Long objectiveId, @ApiParam(value = "the id of the key result", required = true) @PathVariable("keyResultId") Long keyResultId) {
    Objective subjectObjective = objectivesRepository.findByObjectiveId(objectiveId);
    KeyResult subjecKeyResult = subjectObjective.getKeyResults().stream()
            .filter(KeyResult -> keyResultId.equals(KeyResult.getKeyResultId()))
            .findFirst()
            .orElse(null);

    return new ResponseEntity<KeyResult>(subjecKeyResult, HttpStatus.OK);
}

public ResponseEntity<Objective> updateObjective(@ApiParam(value = "id", required = true) @PathVariable("id") Long id, @ApiParam(value = "objective", required = true) @Valid @RequestBody Objective objectiveDTO) {

    Objective existingObjective = objectivesRepository.findByObjectiveId(id);

    Objective objective = ObjectiveBuilder
            .anObjective()
            .withObjectiveId(existingObjective.getObjectiveId())
            .withDescription(objectiveDTO.getDescription())
            .withCompleted(objectiveDTO.getCompleted())
            .withKeyResults(objectiveDTO.getKeyResults())
            .build();

    objective.getKeyResults().forEach(keyResultRepository::save);

    objectivesRepository.save(objective);
    return new ResponseEntity<Objective>(HttpStatus.NO_CONTENT);
}

public ResponseEntity<KeyResult> updateKeyResult(@ApiParam(value = "the id of the objective you want key results for", required = true) @PathVariable("objectiveId") Long objectiveId, @ApiParam(value = "the id of the key result", required = true) @PathVariable("keyResultId") Long keyResultId, @ApiParam(value = "keyResult", required = true) @Valid @RequestBody KeyResult keyResultDTO) {
    if (objectivesRepository.existsById(objectiveId) && keyResultRepository.existsById(keyResultId)) {
        Objective subjectObjective = objectivesRepository.findByObjectiveId(objectiveId);

        KeyResult subjecKeyResult = subjectObjective.getKeyResults().stream()
                .filter(KeyResult -> keyResultId.equals(KeyResult.getKeyResultId()))
                .findFirst()
                .orElse(null);

        KeyResult updatedKeyResult = KeyResultBuilder
                .aKeyResult()
                .withKeyResultId(subjecKeyResult.getKeyResultId())
                .withDescription(keyResultDTO.getDescription())
                .withCompleted(keyResultDTO.getCompleted())
                .build();

        keyResultRepository.save(updatedKeyResult);

        Collections.replaceAll(subjectObjective.getKeyResults(), subjecKeyResult, updatedKeyResult);

        objectivesRepository.save(subjectObjective);
    }

    return new ResponseEntity<KeyResult>(HttpStatus.NO_CONTENT);
}

}

For context on this class, all the AbstractRestHelper super class is doing, is creating singletons of my repositories, which are then .. field injected (unsure if this is the right term) in to the controller. This pattern is repeated across all controllers hence the clutter.

The API being implemented is a Swagger 2 API interface that keeps this controller free of annotations where possible.

The final piece is the test class. This is the core of my question.

@ExtendWith(SpringExtension.class)
@WebMvcTest(ObjectivesApiController.class)
class ObjectivesApiControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private ObjectivesApiController objectivesApiControllerMock;

    @BeforeEach
    void setUp() {
    }

    @AfterEach
    void tearDown() {
    }

    @Test
    void getAllObjectives() throws Exception {
        // Create two objects to test with:

        Objective testObjective1 = ObjectiveBuilder
                .anObjective()
                .withObjectiveId(1L)
                .withDescription("Test Objective")
                .withCompleted(false)
                .build();

        Objective testObjective2 = ObjectiveBuilder
                .anObjective()
                .withObjectiveId(2L)
                .withDescription("Test Objective")
                .withCompleted(true)
                .build();

        List<Objective> testList = new ArrayList<Objective>();
        testList.add(testObjective1);
        testList.add(testObjective2);

        // Set expectations on what should be found:
        when(objectivesApiControllerMock.getAllObjectives()).thenReturn(new ResponseEntity<List<Objective>>(testList, HttpStatus.OK));

        // Carry out the mocked API call:
        mockMvc.perform(get("/objectives"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].objectiveId", is(1)))
                .andExpect(jsonPath("$[0].description", is("Test Objective")))
                .andExpect(jsonPath("$[0].completed", is(false)))
                .andExpect(jsonPath("$[1].objectiveId", is(2)))
                .andExpect(jsonPath("$[1].description", is("Test Objective")))
                .andExpect(jsonPath("$[1].completed", is(true)));


        // Validate the response is what we expect:
        verify(objectivesApiControllerMock, times(1)).getAllObjectives();
        verifyNoMoreInteractions(objectivesApiControllerMock);

    }

    @Test
    void getKeyResultsForObjective() throws Exception {

        KeyResult testKeyResultWithParentObjective1 = KeyResultBuilder
                .aKeyResult()
                .withKeyResultId(1L)
                .withCompleted(false)
                .withDescription("My parent Key Result is 1")
                .build();

        KeyResult testKeyResultWithParentObjective2 = KeyResultBuilder
                .aKeyResult()
                .withKeyResultId(2L)
                .withCompleted(true)
                .withDescription("My parent Key Result is 1")
                .build();

        Objective testObjectiveWithKeyResults = ObjectiveBuilder
                .anObjective()
                .withObjectiveId(1L)
                .withDescription("Test Objective")
                .withKeyResults(new ArrayList<KeyResult>())
                .withCompleted(false)
                .build();

        testObjectiveWithKeyResults.addKeyResult(testKeyResultWithParentObjective1);
        testObjectiveWithKeyResults.addKeyResult(testKeyResultWithParentObjective2);

        when(objectivesApiControllerMock.getKeyResultsForObjective(1L)).thenReturn(new ResponseEntity<List<KeyResult>>(testObjectiveWithKeyResults.getKeyResults(), HttpStatus.OK));

        mockMvc.perform(get("/objectives/1/keyresult"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].keyResultId", is(1)))
                .andExpect(jsonPath("$[0].description", is("My parent Key Result is 1")))
                .andExpect(jsonPath("$[0].completed", is(false)))
                .andExpect(jsonPath("$[1].keyResultId", is(2)))
                .andExpect(jsonPath("$[1].description", is("My parent Key Result is 1")))
                .andExpect(jsonPath("$[1].completed", is(true)));

    }
}

My question is this: Having mocked the objective controller using Mockito to validate that my objects are being formed properly, I now want to do the same thing but instead of mocking, I want to actually test the controller.

What do you think is the most naive way of getting this to work (I can refactor later). The resources I've search through either use different versions of Junit or rely on mockito rather than the actual controller.

Nothing fits quite right - since the controller is mocked, I'm not actually covering any code, and so the tests are worthless right? The only thing I'm looking at is that the objects are formed properly, where I now need to check that the controller is functioning as it should, AND are returning well formed objects.

Has anyone done anything simillar? What approaches do you use to manage the testing of field injected controllers?

Any advice on this would be hugely appreciated. I'd love to learn how people working on production grade applications are handling the testing of Spring Boot Apps with Controllers, Repos, etc.

Thanks so much!


Answer:

You could use @SpyBean. That way you can both use it as it is or mock some calls. https://www.baeldung.com/mockito-spy

Question:

I'm new to unit testing. I'm trying to make tests to a Spring Boot application controller. But the test cannot find my model attribute or something like that. Below you can find my code and hopefully help me discover what I am doing wrong. Thanks in advance!

Failure stack trace:

> java.lang.AssertionError: Model attribute 'restaurants'
Expected: a collection with size <2>
     but: collection size was <0>
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
    at org.springframework.test.web.servlet.result.ModelResultMatchers$1.match(ModelResultMatchers.java:58)
    at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
    at com.matmr.restaurantpoll.controller.RestaurantControllerTest.should_search(RestaurantControllerTest.java:79)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

RestaurantControllerTest.class

package com.matmr.restaurantpoll.controller;

import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;

import java.util.Arrays;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import com.matmr.restaurantpoll.model.Category;
import com.matmr.restaurantpoll.model.Restaurant;
import com.matmr.restaurantpoll.model.filter.RestaurantFilter;
import com.matmr.restaurantpoll.service.RestaurantService;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@WebAppConfiguration
public class RestaurantControllerTest {

    @Mock
    private RestaurantService restaurantService;

    @InjectMocks
    private RestaurantController restaurantController;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders.standaloneSetup(restaurantController).setRemoveSemicolonContent(false).build();

    }

    @Test
    public void should_search() throws Exception {

        RestaurantFilter filter = new RestaurantFilter();
        filter.setName(null);

        Restaurant first = new RestaurantBuilder()
                .id(1L)
                .name("Abra")
                .description("lots of food")
                .category(Category.ITALIAN).build();

        Restaurant second = new RestaurantBuilder()
                .id(2L)
                .name("Kadabra")
                .description("food for days")
                .category(Category.PIZZA).build();

        when(restaurantService.findByNameIgnoreCaseContaining(filter)).thenReturn(Arrays.asList(first, second));

        this.mockMvc.perform(get("/restaurants"))
            .andExpect(status().isOk())
            .andExpect(view().name("restaurantList"))
            .andExpect(model().attribute("restaurants", hasSize(2)))
            .andExpect(model().attribute("restaurants",
                    hasItem(allOf(
                            hasProperty("id", is(1L)),
                            hasProperty("name", is("Abra")),
                            hasProperty("description", is("lots of food"))
                            ))))
            .andExpect(model().attribute("restaurants",
                    hasItem(allOf(
                            hasProperty("id", is(2L)),
                            hasProperty("name", is("Kadabra")),
                            hasProperty("description", is("food for days"))
                            ))));

        verify(restaurantService, times(1)).findByNameIgnoreCaseContaining(filter);
        verifyNoMoreInteractions(restaurantService);

    }

}

RestaurantController.class

package com.matmr.restaurantpoll.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.matmr.restaurantpoll.model.Restaurant;
import com.matmr.restaurantpoll.model.filter.RestaurantFilter;
import com.matmr.restaurantpoll.service.RestaurantService;

@Controller
@RequestMapping("/restaurants")
public class RestaurantController {

    @Autowired
    private RestaurantService restaurantService;

    @RequestMapping
    public ModelAndView pesquisar(@ModelAttribute("filtro") RestaurantFilter filter) {

        List<Restaurant> filterRestaurants = restaurantService.findByNameIgnoreCaseContaining(filter);
        ModelAndView mv = new ModelAndView("restaurantList");
        mv.addObject("restaurants", filterRestaurants);

        return mv;
    }

}

RestaurantList.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org"
    xmlns:layout="http://ultraq.net.nz/thymeleaf/layout"
    layout:decorator="layout">
<head>
<title>Pesquisa de Restaurantes</title>
</head>

<section layout:fragment="conteudo">

    <div layout:include="MensagemGeral"></div>

    <div class="panel panel-default">
        <div class="panel-heading">
            <div class="clearfix">
                <h1 class="panel-title liberty-title-panel">Pesquisa de
                    Restaurantes</h1>
                <a class="btn btn-link liberty-link-panel"
                    th:href="@{/titulos/novo}">Cadastrar Novo Restaurante</a>
            </div>
        </div>

        <div class="panel-body">



            <div class="table-responsive">
                <table class="table table-bordered table-striped">
                    <thead>
                        <tr>
                            <th class="text-center col-md-1">#</th>
                            <th class="text-left col-md-2">Nome</th>
                            <th class="text-left col-md-3">Descrição</th>
                            <th class="text-left col-md-2">Categoria</th>
                            <th class="col-md-1"></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr th:each="restaurant : ${restaurants}">

                            <td class="text-center" th:text="${restaurant.id}"></td>

                            <td class="text-center" th:text="${restaurant.name}"></td>

                            <td th:text="${restaurant.description}"></td>

                            <td th:text="${restaurant.category.description}"></td>

                            <td class="text-center"><a class="btn btn-link btn-xs"
                                th:href="@{/restaurants/{id}(id=${restaurant.id})}"
                                title="Editar" rel="tooltip" data-placement="top"> <span
                                    class="glyphicon glyphicon-pencil"></span>
                            </a> <a class="btn btn-link btn-xs" data-toggle="modal"
                                data-target="#confirmRemove"
                                th:attr="data-id=${restaurant.id}, data-name=${restaurant.name}"
                                title="Excluir" rel="tooltip" data-placement="top"> <span
                                    class="glyphicon glyphicon-remove"></span>
                            </a></td>
                        </tr>
                        <tr>
                            <td colspan="6" th:if="${#lists.isEmpty(restaurants)}">Nenhum
                                restaurante foi encontrado!</td>
                        </tr>
                    </tbody>

                </table>

            </div>
        </div>

        <div layout:include="confirmRemove"></div>

    </div>
</section>
</html>

Answer:

First off, you should remove the annotations from your test class, they are used for integration testing and will only slow your test down.

As far as your problem goes, my guess is that your RestaurantFilter does not implement equals and hashCode so when you use it in

when(restaurantService.findByNameIgnoreCaseContaining(filter))
    .thenReturn(Arrays.asList(first, second));

Mockito actually doesn't match the argument with the mock so it doesn't return the array you've given it. You should either implement equals and hashCode or, faster but possibly more error prone, replace the when(...) definition with:

when(restaurantService.findByNameIgnoreCaseContaining(refEq(filter)))
    .thenReturn(Arrays.asList(first, second));

Matchers.refEq will compare the values using reflection and does not rely on .equals comparison.

Question:

I have controller with the following structure:

@RequestMapping(value = "${foo.controller.requestMappingUrl.login}", method = RequestMethod.POST)
    public ResponseMessage<String> loginUser(
            @RequestParam("username") String username, HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse) {

    try {

       return fooService.login(username); // can mock test

    } catch (UserNotFoundException e) {
            //CANNOT MOCK TEST THIS BLOCK
            String errorMsg = LoggerUtil.printStackTrace(e);
            LOG.error("[RequestId: {}] UserNotFoundException: {}, Stack: {}", requestId, e.getMessage(), errorMsg);
            httpServletResponse.setStatus(HttpStatus.NOT_ACCEPTABLE.value());
            statusCode = StatusCode.UserNotFound.value();
            responseMessage.buildResponseMessage(StringUtils.EMPTY, HttpStatus.NOT_ACCEPTABLE, statusCode,
                    messageByLocaleService.getMessageResponse(statusCode, null, locale));
    }
}

When I mock to throw the exception UserNotFoundException, I get only NestedServletException. Even though I tried adding expected = NestedServletException.class. The corbetura reports indicate that the code block is not covered in testing. Do you have any suggestion to help test the code inside the catch block.

The test code as requested:

@SuppressWarnings("unchecked")
@Test(expected = UserNotFoundException.class)
    public void testControllerUserNotFoundException() throws Exception {
        Response resp = new Response();
        resp.setStatusCode(StatusCode.UserNotFoundErrorCode);
        when(fooService.login(any(String.class)).thenThrow(UserNotFoundException.class);

        mockMvc.perform(post("/service-user/1.0/auth/login?&username=test")
                        .contentType(contentType)).andExpect(status().isNotAcceptable())
                .andExpect(jsonPath("$.statusCode", is("ERRORCODE144")));

    }

And the stack trace

java.lang.Exception: Unexpected exception, expected but was at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:28) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) Caused by: org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.atlassian.crowd.exception.runtime.UserNotFoundException at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) at javax.servlet.http.HttpServlet.service(HttpServlet.java:648) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:65) at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167) at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134) at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:155) at id.co.allianz.microservice.cop.app.auth.controller.AuthControllerTest.testControllerUserNotFoundException(AuthControllerTest.java:105) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:19) ... 22 more Caused by: com.atlassian.crowd.exception.runtime.UserNotFoundException


Answer:

You can't expect UserNotFoundException exception out of the controller method because you are suppressing it by simply logging and returning the response with ERRORCODE144.

The best practice here is to configure the ControllerAdvice so that all of the exceptions can be handled globally and your controller looks clean as shown below, I suggest you also look here on spring controllers exception handling.

ExceptionControllerAdvice class:

@ControllerAdvice
public class ExceptionControllerAdvice {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<String> handleUnexpectedException(UnexpectedException 
                     unExpectedExe) {

        //log and send the response back to the client here
    }
}

Controller method:

@RequestMapping(value = "${foo.controller.requestMappingUrl.login}",
                                 method = RequestMethod.POST)
public ResponseMessage<String> loginUser(
            @RequestParam("username") String username,
               HttpServletRequest httpServletRequest,
               HttpServletResponse httpServletResponse) {

       return fooService.login(username);
}

JUnit setup & test methods:

@Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    mockMvc = MockMvcBuilders.standaloneSetup(projectController)
                .setMessageConverters(new MappingJackson2HttpMessageConverter())
                .setControllerAdvice(new ExceptionControllerAdvice()).build();
}


@SuppressWarnings("unchecked")
@Test
    public void testControllerUserNotFoundException() throws Exception {
        Response resp = new Response();
        resp.setStatusCode(StatusCode.UserNotFoundErrorCode);
        when(fooService.login(any(String.class)).
        thenThrow(UserNotFoundException.class);
        mockMvc.perform(post("/service-user/1.0/auth/login?&username=test")
                        .contentType(contentType)).
     andExpect(status().isNotAcceptable())
                .andExpect(jsonPath("$.statusCode", is("ERRORCODE144")));
}

Question:

Response enitity should be returning a new ResponseEntity with a http status of OK/200. However, during my testing it is returning as null i can see where it is being being set to null but dont understand why or even how. I am sure it's a simple thing i've missed but just cant see it.

As can be seen in the images, the create var is null, however, Mockitio should be setting this to createBlogPostResponse1, so i am not sure why null is being set.

Thanks for any information and help on this.

Test

public static ResponseEntity createBlogPostResponse1 = new ResponseEntity(HttpStatus.OK);


@Test
public void createNewBlogPost() throws Exception {
    String url = TestHelper.URL + "/blogPost/createNewBlogPost";
    when(postService.createNewBlogPost(blogPost1)).thenReturn(TestHelper.createBlogPostResponse1);
    mockMvc.perform(post(url)
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(TestHelper.asJsonString(blogPost1)))
            .andExpect(status().isOk())
            .andReturn();

    verify(postService, times(1)).createNewBlogPost(blogPost1);
    verifyNoMoreInteractions(postService);
}

Controller

ResponseEntity create = postService.createNewBlogPost(cleanBlogPost);

Service

@Override
public ResponseEntity createNewBlogPost(BlogPost createNewBlogPost) {
    return new ResponseEntity(HttpStatus.OK);
}

Answer:

As pointed by JBnizet mockito uses equals method internally to match arguments on your mock method invocation. Try overriding equals method for BlogPost class. If you do not want to override equals and you just want to match any method invocation on your mock - use any() matcher :

    @Test
    public void createNewBlogPost() throws Exception {
        String url = TestHelper.URL + "/blogPost/createNewBlogPost";
        when(postService.createNewBlogPost(Mockito.any(BlogPost.class))).thenReturn(TestHelper.createBlogPostResponse1);
        mockMvc.perform(post(url)
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(TestHelper.asJsonString(blogPost1)))
                .andExpect(status().isOk())
                .andReturn();

        verify(postService, times(1)).createNewBlogPost(Mockito.any(BlogPost.class));
        verifyNoMoreInteractions(postService);
    }

If you want to gain some basic konwledge about matchers try this tutorial.

Question:

All,

Thanks for help. I am pretty new to Mockito, If I have a Service class, a controller class(which using that serivce by passing in a Map param), How can I mock that service method?

Helloworldservice.java

    package service;

    import java.util.Map;

    public class Helloworldservice {
        public String greeting() {
            return "Hello, World";
        }
        public String greetingSB(Map<String, String> sb) {
            return "Hello," + sb.get("name");
        }
    }

Helloworldcontroller.java

    package controller;

    import java.util.HashMap;
    import java.util.Map;

    import service.Helloworldservice;

    public class Helloworldcontroller {
        private Helloworldservice hservice;

        public Helloworldcontroller() {
            // TODO Auto-generated constructor stub
            hservice = new Helloworldservice();
        }

        public String sayHello() {
            return hservice.greeting();
        }

        public String sayHelloSB() {
            Map<String, String> sb = new HashMap<String, String>();
            sb.put("name", "somebody");
            return hservice.greetingSB(sb);
        }
    }

HelloworldcontrollerTest.java

    package unit.controller;

    import org.junit.Assert;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.Mockito;
    import org.mockito.junit.MockitoJUnitRunner;

    import controller.Helloworldcontroller;
    import service.Helloworldservice;

    @RunWith(MockitoJUnitRunner.class)
    public class HelloworldcontrollerTest {
        @InjectMocks
        private Helloworldcontroller hcontroller;

        private Helloworldservice hservice = new Helloworldservice();
        @Mock
        private Helloworldservice hservice_mock;
        @Before
        public void setup() {
            hservice_mock = Mockito.spy(hservice);
            /**  I am not sure how to mock here for that param sb
            Mockito.when(hservice_mock.greetingSB(.......))
                    .thenReturn("Hello, somebody");

            **/
        }

        @Test
        public void testGreeting() {
            String h = hcontroller.sayHelloSB();
            Assert.assertEquals(h, "Hello, sombody!!!");
        }
    }

The serice always returns null, I am not sure what is wrong.


Answer:

Your sample code is a bit off as it'll work without any mocking involved.

public class HelloworldcontrollerTest {
    private Helloworldcontroller hcontroller = new Helloworldcontroller();

    @Test
    public void testGreeting() {
        String h = hcontroller.sayHelloSB();
        Assert.assertEquals(h, "Hello,somebody");
    }
}

Anyway, it's probably just that, a sample.

The problem with mocking lies with this line

hservice_mock = Mockito.spy(hservice);

First, you let Mockito create your mock (@Mock Helloworldservice hservice_mock) and inject it into the controller (@InjectMocks Helloworldcontroller hcontroller) and then you're creating a spy on your own (hservice_mock = Mockito.spy(hservice)) for which you try to setup your expectations (when(hservice_mock.greetingSB(...))). Setting up expectations on spies require a different method invocation chain, hence a NullPointerException would occur currently (see Important gotcha on spying real objects!). Even if it would work, it wouldn't affect the already injected mock.

This would work as expected:

@RunWith(MockitoJUnitRunner.class)
public class HelloworldcontrollerTest {
    @InjectMocks
    private Helloworldcontroller hcontroller = new Helloworldcontroller();

    @Mock
    private Helloworldservice hservice_mock;

    @Before
    public void setup() {
        Mockito.when(hservice_mock.greetingSB(any(Map.class)))
                .thenReturn("Hello, somebody!!!");
    }

    @Test
    public void testGreeting() {
        String h = hcontroller.sayHelloSB();
        Assert.assertEquals(h, "Hello, somebody!!!");
    }
}

A few other comments on the test setup:

  • Helloworldcontroller has dependency to Helloworldservice. Instead of creating the instance in the constructor, you should consider using constructor dependency injection. Your sample code works but things go out of control should it become more complex. Think of database access.
  • @InjectMocks is a sign of code smell and indicates a deeper problem with the code to be tested. Try to avoid if possible.
  • The first argument of assertEquals as in assertEquals(h, "Hello, sombody!!!") is the expected value, then comes the actual value. Sounds not very important, but affects assertion violation error message.

Let's tackle those problems and see how we can improve the code.

First, use constructor injection.

public class Helloworldcontroller {
    private final Helloworldservice hservice;

    public Helloworldcontroller(Helloworldservice hservice) {
        this.hservice = hservice;
    }

    public String sayHello() {
        return hservice.greeting();
    }

    public String sayHelloSB() {
        Map<String, String> sb = new HashMap<String, String>();
        sb.put("name", "somebody");
        return hservice.greetingSB(sb);
    }
}

The test code becomes now

@RunWith(MockitoJUnitRunner.class)
public class HelloworldcontrollerTest {
    private Helloworldcontroller hcontroller;

    @Mock
    private Helloworldservice hservice;

    @Before
    public void setup() {
        hcontroller = new Helloworldcontroller(hservice);

        Mockito.when(hservice.greetingSB(any(Map.class)))
                .thenReturn("Hello, somebody!!!");
    }

    @Test
    public void testGreeting() {
        Assert.assertEquals("Hello, somebody!!!", hcontroller.sayHelloSB());
    }
}