Announcement Announcement Module
Collapse
No announcement yet.
How to mock ApplicationConversionServiceFactoryBean in junit4 tests? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • How to mock ApplicationConversionServiceFactoryBean in junit4 tests?

    I am testing my controllers using
    Code:
    AnnotationMethodHandlerAdapter
    One of my controller method looks like:
    Code:
    @RequestMapping(value = "/")
    public String start(@Validated MyType myType, BindingResult errors) {
      ...
    }
    MyType sample:
    Code:
    @RooJavaBean
    @RooToString
    @RooJpaActiveRecord
    public class MyType {
    
      @OneToOne
      @JoinColumn(unique = true, nullable = false)
      private InnerType innerType;
    
    }
    Sample test class looks like:
    Code:
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "/META-INF/spring/MyTest-context.xml" })
    class ControllerTest{
      @Autowired
      private AnnotationMethodHandlerAdapter handlerAdapter;
    
      private MockHttpServletRequest request;
      private MockHttpServletResponse response;
     
      @Before
      public void resetMockHttpServelt() {
        this.request = new MockHttpServletRequest();
        this.response = new MockHttpServletResponse();
      }
    
      @Test
      public void test(){
        request.setMethod("GET");
        request.setRequestURI("/");
        request.setParameter("innerType", 4);
    
        handlerAdapter.handle(request,response,controller);
        ...
      }
    }
    When application is deployed to tomcat innerType is injected by its id but in my tests it doesn't work. I discovered that converters are responsible for this. But how to mock service factory bean ?

    PS. Some time ago i removed all of default entities controllers generated by roo - I found out that roo also removed ApplicationConversionServiceFactoryBean_Roo_Conver sionService.aj. So where the injection takes place ?

    Sorry for my english... I am quite tired with this one

    Thanks in advance...

  • #2
    It feels like there is a mixup of levels of testing here. What is your test method trying to prove, that you can resolve the URL and run the controller method? Do you need to write tests for all URIs you provide to test those? Or is your goal to test the controller methods and you are just doing it one layer above?

    Also, the injection of a @OneToOne isn't being done by Tomcat, but by a running Spring / JPA platform. The reason your test isn't injecting the one to one mapping is because you're only mounting the web layer - you'd have to also add classpath:META-INF/spring/applicationContext*.xml to your configuration above to boot the persistence layer.

    If you're trying to set innerType from a form post or GET, you have to define the path properly. If you are accepting:

    @Validated MyType myType, BindingResult errors

    Then I believe you'd need a field that is named myType.innerType in the form POST / GET. Your MVC form has a modelAttribute or commandName attribute you fill in with this top-level path name, and the name needs to match the one in your form bean definition in the method (or the annotation @ModelAttribute("beanPathName") so that it knows the basis for the request).

    So, the innerType instance in the database is actually filled in when you retrieve the outer type instance (by a primary key) and then access the innerType. It's a big subject, but the innerType can be forced to load (by setting it to fetch instead of lazy load) and that's a JPA topic, not a Spring one.

    You use formatters and converters to convert / format data at the web tier. To/from web forms. Things like @FormatDateTime and number/currency formatting, etc...

    The main reason to inject the actual type in the method as you've shown in your controller method is to de-couple the code from most if not all of the servlet API. That makes your actual methods easier to test.

    So, in that case, you can unit test, mock the lower layers, and test the method itself, not the resolver:

    Code:
     @Test
      public void test(){
    
         // to mock the errors object:
    
         BindingResult mockBindingResult = // you can use EasyMock, Mockito, etc. to create a mock, 
         // see http://stackoverflow.com/questions/8299607/junit-testing-for-annotated-controller
    
         InnerBean innerBean = new InnerBean();
         innerBean.setId(4);
    
         MyBean myBean = new MyBean();
         myBean.setInnerBean(innerBean); 
         MyController controller = new MyController();
         String result = controller.start(myBean, mockBindingResult);
        ...
      }
    Sorry, this is rambling, but maybe some of this is some use.

    Comment


    • #3
      I would like not to use mocked binding result object because MyClass has many inner fields whose should be validated and my flow depends on validation result. (Some not filled will rise an exception some warning, some can be misstyped etc.). I previously use mock for binding result but then each time i should set a field in myClass object and set error for that field in bindingResult mock.

      Code:
      @Mock
      BindingResult errors;
      
       @Test(expected = DigitisOnlyException.class)
        public void DigitisOnlyExceptionException() throws Exception {
           MyType myType = new MyType.builder.field1("digits only field").field2(43234).field3("435345345653345").build();
           when(errors.hasFieldErrors("field1")).thenReturn(true);
      
           // when
           controller.start(myType, errors);
        }
      And for example for inner fields we have 2^3 combinations

      Some of my controllers methods has two parameters that sould be validated each of them has more then 3 fields.

      Insead my test looks like (after writing some methods same for all tests):
      Code:
       @Test(expected = DigitisOnlyException.class)
        public void DigitisOnlyExceptionException() throws Exception {
          setRequestParameter("field1", "digits only field");
          setRequestParameter("field2", 43234);
          setRequestParameter("field3", "435345345653345");
      
          // when
          handle(CHECK);
      }
      Last edited by damian0o; Sep 27th, 2012, 04:53 AM.

      Comment


      • #4
        Maybe you could create an instance of BeanPropertyBindingResult for your bean in question and call the controller method, not the handlerAdapter?

        Something like:

        Code:
        MyBean bean = new MyBean(...);
        
        BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(bean, "bean");
        ...
        String result = myController.method(bean, bindingResult);
        
        ...
        Ken

        Comment


        • #5
          Thank You very much for your reply.

          Above solution is looking great but how to populate binding result object because constructor alone only set fields in BeanPropertyBindingResult. (It seems that i must create concrete validator for every bean... )

          Comment

          Working...
          X