WildDev
WildDev

Reputation: 2367

Spring Boot 1.4 - how to test a controller with the validation

Spring Boot 1.4 has a number of fine features including @DataJpaTest annotation that automatically wakes up the classpath embedded database for the test purposes. As far I know, it won't work in conjuction with TestRestTemplate in bounds of the same class.

The following test won't work:

@RunWith(SpringRunner.class)
@SpringBootTest
@DataJpaTest
public class PersonControllerTest {

    private Logger log = Logger.getLogger(getClass());

    private Category category;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private TestEntityManager entityManager;

    @Before
    public void init() {

        log.info("Initializing...");

        category = entityManager.persist(new Category("Staff"));
    }

    @Test
    public void personAddTest() throws Exception {

        log.info("PersonAdd test starting...");

        PersonRequest request = new PersonRequest("Jimmy");

        ResponseEntity<String> response = restTemplate.postForEntity("/Person/Add", request, String.class);
        assertEquals(HttpStatus.OK, response.getStatusCode());

        log.info("PersonAdd test passed");
    }

During startup of the test an exception will be thrown:

Unsatisfied dependency expressed through field 'restTemplate': 
No qualifying bean of type [org.springframework.boot.test.web.client.TestRestTemplate]

Then guessing to switch to the recommended mock based slice approach but it won't work there because the controller looks like this:

@RequestMapping(value="/Person/Add", method=RequestMethod.POST)
public ResponseEntity personAdd(@Valid @RequestBody PersonRequest personRequest, 
                                Errors errors) 

     personValidator.validate(personRequest, errors):

     if (errors.hasErrors())
         return new ResponseEntity(HttpStatus.BAD_REQUEST);

     personService.add(personRequest);

     return new ResponseEntity(HttpStatus.OK);
}

... it's easy to mock the personService as the documentation suggests but how to be with the errors object which is not mockable in this case? As far I know, there's no ways to mock it since it isn't class field or a returned value of a method.

So, I'm unable to test the code above using neither slice approach nor integration one since @DataJpaTest should not be used with a controller.

Is there a way to test the controller with such architecture using Spring Boot 1.4 testing features?

Upvotes: 1

Views: 7293

Answers (1)

Shawn Clark
Shawn Clark

Reputation: 3440

Your understanding of the @DataJpaTest is a little off. From the documentation "Can be used when a test focuses only on JPA components". If you are wanting to test your controller layer you don't want to use this annotation as none of the WebMvc components get loaded into the application context. You instead want to use the @WebMvcTest and have it use the @Controller that you are testing.

@RunWith(SpringRunner.class)
@WebMvcTest(PersonController.class)
public class PersonControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    PersonValidator personValidator;

    @MockBean
    PersonService personService;

    @Test
    public void personAddTest() throws Exception {
        String content = "{\"name\": \"Jimmy\"}";
        mockMvc.perform(post("/Person/Add").contentType(MediaType.APPLICATION_JSON).characterEncoding("UTF-8")
                .accept(MediaType.APPLICATION_JSON).content(content)).andExpect(status().isOk());
    }

    @Test
    public void personAddInvalidTest() throws Exception {
        String content = "{\"noname\": \"Jimmy\"}";
        mockMvc.perform(post("/Person/Add").contentType(MediaType.APPLICATION_JSON).characterEncoding("UTF-8")
                .accept(MediaType.APPLICATION_JSON).content(content)).andExpect(status().isBadRequest());
    }
}

Not sure how you wired the validator and service so just assumed you autowired them.

@Controller
public class PersonController {
    private PersonValidator personValidator;
    private PersonService personService;

    public PersonController(PersonValidator personValidator, PersonService personService) {
        this.personValidator = personValidator;
        this.personService = personService;
    }

    @RequestMapping(value = "/Person/Add", method = RequestMethod.POST)
    public ResponseEntity<String> personAdd(@Valid @RequestBody PersonRequest personRequest, Errors errors) {
        personValidator.validate(personRequest, errors);

        if (errors.hasErrors()) {
            return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
        }
        personService.add(personRequest);

        return new ResponseEntity<String>(HttpStatus.OK);
    }
}

Sample PersonRequest as I didn't know what else was in there. Note the one validation on the name as being @NotNull as I wanted a way to show how to use the Errors object.

public class PersonRequest {
    @NotNull
    private String name;

    public PersonRequest() {
    }

    public PersonRequest(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Upvotes: 4

Related Questions