Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

@ModelAttribute controller spring-mvc mocking

I want to test a controller which is using @ModelAttribute for one of its method arguments.

public String processSaveAction(@ModelAttribute("exampleEntity") ExampleEntity exampleEntity)

@ModelAttribute method getExampleEntity is using @RequestParam:

@ModelAttribute("exampleEntity")
public ExampleEntity getExampleEntity(@RequestParam(value = "id", required = true) ExampleEntity exampleEntity) {

My controller is using WebDataBinder to call a factory, which returns an object based on param "id".

@Controller
public class ExampleController(){

    @Autowired private IdEditorFactory idEditorFactory;

    @InitBinder
    public void initBinder(WebDataBinder binder) {

        binder.registerCustomEditor(ExampleEntity.class, idEditorFactory.createEditor(ExampleEntity.class));
    }

    @ModelAttribute("exampleEntity")
    public ExampleEntity getExampleEntity(@RequestParam(value = "id", required = true) ExampleEntity exampleEntity) {

        //Irrelevant operations
        return exampleEntity;
    }

    @RequestMapping(method = RequestMethod.POST, params = "action=save")
    public String processSaveAction(
            @RequestParam(value = "confirmed") String exampleString,
            @ModelAttribute("exampleEntity") ExampleEntity exampleEntity,
            BindingResult result, HttpServletRequest request)
            throws IOException {

        boolean success = editorProcessor.processSaveAction(exampleString,
                exampleEntity, result, request);

        return success ? getSuccessView(exampleEntity) : VIEW_NAME;
    }
}

And my test:

@WebAppConfiguration
public class ExampleControllerTest{

    @Mock private EditorProcessor editorProcessor;
    @Mock private IdEditorFactory idEditorFactory;
    @InjectMocks private ExampleController exampleController;

    private MockMvc mockMvc;


    @Before
    public void setUp() throws Exception {

        MockitoAnnotations.initMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(exampleController).build();

        WebDataBinder webDataBinder = new WebDataBinder(ExampleEntity.class);
        webDataBinder.registerCustomEditor(ExampleEntity.class, idEditorFactory.createEditor(ExampleEntity.class));
    }

    @Test
    public void shouldProcessSaveAction() throws Exception {

        // given
        BindingResult result = mock(BindingResult.class);
        ExampleEntity exampleEntity = mock(ExampleEntity.class);
        HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);

        given(editorProcessor.processSaveAction("confirmed", exampleEntity, result, httpServletRequest)).willReturn(true);

        // when
        ResultActions perform = mockMvc.perform(post("/").sessionAttr("exampleEntity", exampleEntity)
                                                            .param("id", "123456"
                                                            .param("action","save"));

        // then
        perform.andDo(print())
                .andExpect(status().isOk());

    }
}

I want to somehow mock getExampleEntity() so that every time I perform a POST with parameter "id", I receive a mocked object ("exampleEntity") for the test.

I could introduce @Binding to the test, but then I would have to mock many levels of methods (like initBinder -> idEditoryFactory-> editor -> hibernateTemplate and so on) only to get an entity from some source (for example, a database).

like image 872
Remik Avatar asked Dec 15 '14 11:12

Remik


2 Answers

You can pass in the required @ModelAttribute object with the .flashAttr() method like so:

mockMvc.perform(post("/")                                                           
    .param("id", "123456")
    .param("action","save")
    .flashAttr("exampleEntity", new ExampleEntity()));
like image 94
Dan P Avatar answered Sep 23 '22 03:09

Dan P


First, test code shouldn't change our development code. @ModelAttribute will be mount from your param attribute, so .param() is enough. Below is my demo:

    @Test
    public void registerUser() throws Exception {
        System.out.println("hello......." + rob.toString());
        RequestBuilder request = post("/register.html")
            .param("username", rob.getUsername())
            .param("password", rob.getPassword())
            .param("firstName", rob.getFirstName())
            .param("lastName", rob.getLastName())
            .param("email", rob.getEmail())
            .with(csrf());

        mvc
            .perform(request)
            .andDo(MockMvcResultHandlers.print())
            .andExpect(redirectedUrl("/"));
    }

Then is my @Controller:

@Controller
public class LoginController {
    @Autowired
    private UserService userService;

    @RequestMapping(value = "/remove", method = RequestMethod.GET)
    public String removeById(@RequestParam("userid") int id, RedirectAttributes attr) {
        attr.addFlashAttribute("message", "remove!!!");
        attr.addAttribute("mess", "remove ");
        return "redirect:/userlist.html";
    }

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String register(@ModelAttribute("user") User user, ModelMap model) {
        System.out.println("register " + user.toString());
        boolean result = userService.add(user);
        model.addAttribute("message", "add " + (result ? "successed" : "failed") + "!!!");
        return "/";
    }
}

This can submit the right user object to the public String register(@ModelAttribute("user") User user, ModelMap model).

like image 38
Jianchao Lee Avatar answered Sep 22 '22 03:09

Jianchao Lee