View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   */
19  package org.apache.myfaces.trinidad.component;
20  
21  import java.beans.BeanInfo;
22  import java.beans.IntrospectionException;
23  import java.beans.Introspector;
24  import java.beans.PropertyDescriptor;
25  
26  import java.io.IOException;
27  
28  import java.lang.reflect.InvocationTargetException;
29  import java.lang.reflect.Method;
30  
31  import java.util.Collections;
32  import java.util.Map;
33  
34  import javax.faces.component.EditableValueHolder;
35  import javax.faces.component.UIComponent;
36  import javax.faces.component.UIViewRoot;
37  import javax.faces.component.ValueHolder;
38  import javax.faces.context.FacesContext;
39  import javax.faces.convert.Converter;
40  import javax.faces.el.ValueBinding;
41  import javax.faces.event.FacesEvent;
42  import javax.faces.event.ValueChangeListener;
43  import javax.faces.render.RenderKit;
44  import javax.faces.render.RenderKitFactory;
45  import javax.faces.render.Renderer;
46  import javax.faces.validator.Validator;
47  
48  import junit.framework.Test;
49  import junit.framework.TestSuite;
50  
51  import org.apache.myfaces.trinidad.context.MockRequestContext;
52  import org.apache.myfaces.trinidad.event.AttributeChangeEvent;
53  import org.apache.myfaces.trinidadbuild.test.FacesTestCase;
54  
55  import org.jmock.Mock;
56  import org.jmock.core.Constraint;
57  
58  
59  /**
60   * Base class for JavaServer Faces UIComponent unit tests.
61   *
62   */
63  public class UIComponentTestCase extends FacesTestCase
64  {
65    /**
66     * Creates a new UIComponentTestCase.
67     *
68     * @param testName  the unit test name
69     */
70    public UIComponentTestCase(
71      String testName)
72    {
73      super(testName);
74    }
75  
76    @Override
77    protected void setUp() throws Exception
78    {
79      _mockRequestContext = new MockRequestContext();
80      super.setUp();
81    }
82  
83    @Override
84    protected void tearDown() throws Exception
85    {
86      super.tearDown();
87      _mockRequestContext.release();
88      _mockRequestContext = null;
89    }
90  
91    public static Test suite()
92    {
93      return new TestSuite(UIComponentTestCase.class);
94    }
95  
96    /**
97     * Tests the transparency of the component attribute by comparing
98     * bean accessor and mutator methods with attribute map accessor
99     * and mutator methods.
100    *
101    * @param component   the component with attribute map
102    * @param attrName    the attribute name to test
103    * @param attrValue   the value for use by the attribute map mutator
104    * @param propValue   the value for use by the bean mutator
105    */
106   @SuppressWarnings("unchecked")
107   protected void doTestAttributeTransparency(
108     UIComponent component,
109     String      attrName,
110     Object      attrValue,
111     Object      propValue)
112   {
113     assertFalse("Test values for attribute \"" + attrName + "\" must differ",
114                 (attrValue == propValue ||
115                  (attrValue != null &&
116                   attrValue.equals(propValue))));
117 
118     Map<String, Object> attrMap = component.getAttributes();
119     try
120     {
121       boolean foundProperty = false;
122       // bean info is cached
123       BeanInfo info = Introspector.getBeanInfo(component.getClass());
124       PropertyDescriptor[] pds = info.getPropertyDescriptors();
125       for (int i=0; i < pds.length; i++)
126       {
127         String propName = pds[i].getName();
128         if (attrName.equals(propName))
129         {
130           if (pds[i].getPropertyType().isPrimitive())
131           {
132             assertNotNull("Primitive \"" + attrName +
133                           "\" attribute value must be non-null",
134                           attrValue);
135             assertNotNull("Primitive \"" + propName +
136                           "\" property value must be non-null",
137                           propValue);
138           }
139 
140           foundProperty = true;
141 
142           Method reader = pds[i].getReadMethod();
143           Method writer = pds[i].getWriteMethod();
144           writer.invoke(component, new Object[] { propValue });
145           assertEquals("Property set not visible in attribute map",
146                        attrMap.get(attrName), propValue);
147           attrMap.put(attrName, attrValue);
148           assertEquals("Attribute put not visible in property value",
149                        reader.invoke(component, new Object[0]), attrValue);
150           break;
151         }
152       }
153 
154       if (!foundProperty)
155         fail("Unable to find attribute property \"" + attrName + "\"");
156     }
157     catch (IntrospectionException e)
158     {
159       e.printStackTrace();
160       fail("Unable to access attribute property \"" + attrName + "\"");
161     }
162     catch (InvocationTargetException e)
163     {
164       e.printStackTrace();
165       fail("Unable to access attribute property \"" + attrName + "\"");
166     }
167     catch (IllegalAccessException e)
168     {
169       e.printStackTrace();
170       fail("Unable to access attribute property \"" + attrName + "\"");
171     }
172   }
173 
174   /**
175    * Tests the transparency of the component facet by comparing
176    * bean accessor and mutator methods with facet map accessor
177    * and mutator methods.
178    *
179    * @param component   the component with attribute map
180    * @param facetName   the facet name to test
181    * @param facetValue  the value for use by the facet map mutator
182    * @param propValue   the value for use by the bean mutator
183    */
184   @SuppressWarnings("unchecked")
185   protected void doTestFacetTransparency(
186     UIComponent component,
187     String      facetName)
188   {
189     Mock mockFacetValue = mock(UIComponent.class);
190     UIComponent facetValue = (UIComponent) mockFacetValue.proxy();
191     mockFacetValue.stubs().method("getParent").will(returnValue(null));
192     mockFacetValue.stubs().method("setParent");
193 
194     Mock mockPropValue = mock(UIComponent.class);
195     UIComponent propValue = (UIComponent) mockPropValue.proxy();
196     mockPropValue.stubs().method("getParent").will(returnValue(null));
197     mockPropValue.stubs().method("setParent");
198 
199     Map<String, UIComponent> facetMap = component.getFacets();
200     try
201     {
202       // bean info is cached
203       BeanInfo info = Introspector.getBeanInfo(component.getClass());
204       PropertyDescriptor[] pds = info.getPropertyDescriptors();
205       boolean foundProperty = false;
206       for (int i=0; i < pds.length; i++)
207       {
208         String propName = pds[i].getName();
209         if (facetName.equals(propName))
210         {
211           assertTrue("Facet bean accessor must return UIComponent or subclass",
212             UIComponent.class.isAssignableFrom(pds[i].getPropertyType()));
213 
214           foundProperty = true;
215 
216           Method reader = pds[i].getReadMethod();
217           Method writer = pds[i].getWriteMethod();
218           writer.invoke(component, new Object[] { propValue });
219           assertEquals("Property set not visible in facet map",
220                        facetMap.get(facetName), propValue);
221           facetMap.put(facetName, facetValue);
222           assertEquals("Facet put not visible in property value",
223                        reader.invoke(component, new Object[0]), facetValue);
224           break;
225         }
226       }
227 
228       if (!foundProperty)
229         fail("Unable to find facet property \"" + facetName + "\"");
230     }
231     catch (IntrospectionException e)
232     {
233       e.printStackTrace();
234       fail("Unable to access facet property \"" + facetName + "\"");
235     }
236     catch (InvocationTargetException e)
237     {
238       e.printStackTrace();
239       fail("Unable to access facet property \"" + facetName + "\"");
240     }
241     catch (IllegalAccessException e)
242     {
243       e.printStackTrace();
244       fail("Unable to access facet property \"" + facetName + "\"");
245     }
246     finally
247     {
248       mockFacetValue.verify();
249       mockPropValue.verify();
250     }
251   }
252 
253   /**
254    * Tests the apply-request-values lifecycle phase.
255    */
256   protected void doTestApplyRequestValues(
257     UIComponent component)
258   {
259     UIViewRoot root = new UIViewRoot();
260     doTestApplyRequestValues(root, component);
261   }
262 
263   /**
264    * Tests the apply-request-values lifecycle phase.
265    */
266   protected void doTestApplyRequestValues(
267     UIViewRoot  root,
268     UIComponent component)
269   {
270 
271     Mock mockRenderKitFactory = mock(RenderKitFactory.class);
272 
273     Mock mockRenderkit = getMockRenderKitWrapper().getMock();
274     RenderKit renderkit = getMockRenderKitWrapper().getRenderKit();
275 
276     Mock mockRenderer = mock(Renderer.class);
277     Renderer renderer = (Renderer) mockRenderer.proxy();
278 
279     mockRenderKitFactory.stubs().method("getRenderKit").will(returnValue(renderkit));
280     mockRenderkit.stubs().method("getRenderer").will(returnValue(renderer));
281 
282     if (isRendererUsed() && component.isRendered())
283     {
284       mockRenderer.expects(once()).method("decode");
285     }
286     else
287     {
288       mockRenderer.expects(never()).method("decode");
289     }
290 
291     try
292     {
293       setCurrentContext(facesContext);
294       doTestApplyRequestValues(facesContext, root, component);
295     }
296     finally
297     {
298       setCurrentContext(null);
299     }
300 
301     mockRenderKitFactory.verify();
302     mockRenderkit.verify();
303     mockRenderer.verify();
304   }
305 
306 
307   @SuppressWarnings("unchecked")
308   protected void doTestApplyRequestValues(
309     FacesContext context,
310     UIViewRoot   root,
311     UIComponent  component)
312   {
313 
314     Mock mock = createMockUIComponent();
315     UIComponent child = (UIComponent) mock.proxy();
316 
317     // JavaServer Faces 1.0 Specification, section 2.2.2
318     // During the apply-request-values phase,
319     // only the processDecodes lifecycle method may be called.
320     if (willChildrenBeProcessed(component))
321       mock.expects(once()).method("processDecodes");
322 
323     // construct the UIComponent tree and
324     // execute the apply-request-values lifecycle phase
325     if (component.getParent() == null)
326       root.getChildren().add(component);
327 
328     component.getChildren().add(child);
329 
330     AttributeChangeTester attributeChangeTester = null;
331     if (component instanceof UIXComponent)
332     {
333       attributeChangeTester = new AttributeChangeTester();
334       ((UIXComponent) component).setAttributeChangeListener(attributeChangeTester);
335       ((UIXComponent) component).addAttributeChangeListener(attributeChangeTester);
336       AttributeChangeEvent ace =
337         new AttributeChangeEvent(component, "testProperty",
338                                  Boolean.FALSE, Boolean.TRUE);
339       ace.queue();
340     }
341 
342     root.processDecodes(context);
343 
344     if (attributeChangeTester != null)
345       attributeChangeTester.verify();
346 
347     mock.verify();
348   }
349 
350   /**
351    * Tests the process-validations lifecycle phase.
352    */
353   protected void doTestProcessValidations(
354     UIComponent component)
355   {
356     doTestProcessValidations(component, "submittedValue", "convertedValue");
357   }
358 
359   /**
360    * Tests the apply-request-values lifecycle phase.
361    */
362   protected void doTestProcessValidations(
363     UIComponent component,
364     Object      submittedValue,
365     Object      convertedValue)
366   {
367     UIViewRoot root = new UIViewRoot();
368     doTestProcessValidations(root, component, submittedValue, convertedValue);
369   }
370 
371   /**
372    * Tests the process-validations lifecycle phase.
373    */
374   protected void doTestProcessValidations(
375     UIViewRoot  root,
376     UIComponent component,
377     Object      submittedValue,
378     Object      convertedValue)
379   {
380 
381     Mock mockRenderKit = getMockRenderKitWrapper().getMock();
382 
383     Mock mockRenderer = mock(Renderer.class);
384     Renderer renderer = (Renderer) mockRenderer.proxy();
385     mockRenderKit.stubs().method("getRenderer").will(returnValue(renderer));
386 
387     Mock mockConverter = mock(Converter.class);
388     Converter converter = (Converter) mockConverter.proxy();
389 
390     Mock mockValidator = mock(Validator.class);
391     Validator validator = (Validator) mockValidator.proxy();
392 
393     Mock mockListener = mock(ValueChangeListener.class);
394     ValueChangeListener listener = (ValueChangeListener) mockListener.proxy();
395 
396     setCurrentContext(facesContext);
397 
398     // if the component is an EditableValueHolder, then the submitted value
399     // must be converted and validated before this phase completes.
400     if (component instanceof EditableValueHolder)
401     {
402 
403       EditableValueHolder editable = (EditableValueHolder)component;
404       mockConverter.expects(never()).method("getAsObject");
405       mockConverter.expects(never()).method("getAsString");
406       mockRenderer.expects(once()).method("getConvertedValue").will(returnValue(convertedValue));
407       editable.setConverter(converter);
408       editable.setSubmittedValue(submittedValue);
409       editable.addValidator(validator);
410       editable.addValueChangeListener(listener);
411 
412       mockListener.expects(once()).method("processValueChange");
413       mockValidator.expects(once()).method("validate").with(new Constraint[]  { eq(facesContext), eq(component), eq(convertedValue) });
414 
415     }
416     // if the component is a ValueHolder, then the value is not updated or
417     // validated and no value change event occurs.
418     else if (component instanceof ValueHolder)
419     {
420       ValueHolder holder = (ValueHolder)component;
421       holder.setConverter(converter);
422       mockConverter.expects(never()).method("getAsObject");//setExpectedGetAsObjectCalls(0);
423       mockConverter.expects(never()).method("getAsString");
424     }
425 
426     doTestProcessValidations(facesContext, root, component);
427 
428     mockRenderKit.verify();
429     mockRenderer.verify();
430     mockConverter.verify();
431     mockValidator.verify();
432     mockListener.verify();
433 
434     setCurrentContext(null);
435   }
436 
437 
438   @SuppressWarnings("unchecked")
439   protected void doTestProcessValidations(
440     FacesContext context,
441     UIViewRoot   root,
442     UIComponent  component)
443   {
444 
445     Mock mock = createMockUIComponent();
446     UIComponent child = (UIComponent) mock.proxy();
447 
448     // JavaServer Faces 1.0 Specification, section 2.2.3
449     // During the process-validations phase,
450     // only the processValidators lifecycle method may be called.
451     if (willChildrenBeProcessed(component))
452       mock.expects(once()).method("processValidators");
453 
454     // construct the UIComponent tree and
455     // execute the apply-request-values lifecycle phase
456     if (component.getParent() == null)
457       root.getChildren().add(component);
458     component.getChildren().add(child);
459 
460     root.processValidators(context);
461 
462     mock.verify();
463   }
464 
465   /**
466    * Tests the update-model-values lifecycle phase.
467    */
468   protected void doTestUpdateModelValues(
469     UIComponent component)
470   {
471     UIViewRoot root = new UIViewRoot();
472     doTestUpdateModelValues(root, component);
473   }
474 
475   /**
476    * Tests the update-model-values lifecycle phase.
477    */
478   protected void doTestUpdateModelValues(
479     UIViewRoot  root,
480     UIComponent component)
481   {
482 
483     Mock mockRenderkit = getMockRenderKitWrapper().getMock();
484 
485     Mock mockRenderer = mock(Renderer.class);
486     Renderer renderer = (Renderer) mockRenderer.proxy();
487     mockRenderkit.stubs().method("getRenderer").will(returnValue(renderer));
488 
489     Mock mockBinding = mock(ValueBinding.class);
490     ValueBinding binding = (ValueBinding) mockBinding.proxy();
491 
492     setCurrentContext(facesContext);
493 
494     // if the component is an EditableValueHolder, then the value binding
495     // must be updated with the new value before this phase completes.
496     if (component instanceof EditableValueHolder)
497     {
498       EditableValueHolder editable = (EditableValueHolder)component;
499       component.setValueBinding("value", binding);
500       editable.setValue("newValue");
501       mockBinding.expects(atLeastOnce()).method("setValue").with(eq(facesContext), eq("newValue"));
502 
503       assertEquals(true, editable.isLocalValueSet());
504     }
505 
506     doTestUpdateModelValues(facesContext, root, component);
507 
508     setCurrentContext(null);
509 
510     mockRenderer.verify();
511     mockBinding.verify();
512   }
513 
514 
515   @SuppressWarnings("unchecked")
516   protected void doTestUpdateModelValues(
517     FacesContext context,
518     UIViewRoot   root,
519     UIComponent  component)
520   {
521     Mock mock = createMockUIComponent();
522     UIComponent child = (UIComponent) mock.proxy();
523 
524     // JavaServer Faces 1.0 Specification, section 2.2.4
525     // During the update-model-values phase,
526     // only the processUpdates lifecycle method may be called.
527     if (willChildrenBeProcessed(component))
528       mock.expects(once()).method("processUpdates");
529 
530     // construct the UIComponent tree and
531     // execute the apply-request-values lifecycle phase
532     if (component.getParent() == null)
533       root.getChildren().add(component);
534     component.getChildren().add(child);
535     root.processUpdates(context);
536 
537     mock.verify();
538   }
539 
540   /**
541    * Tests the invoke-application lifecycle phase.
542    */
543   protected void doTestInvokeApplication(
544     UIComponent   component,
545     FacesEvent    event)
546   {
547     try
548     {
549       setCurrentContext(facesContext);
550 
551       doTestInvokeApplication(facesContext, facesContext.getViewRoot(), component, event);
552 
553     }
554     finally
555     {
556       setCurrentContext(null);
557     }
558   }
559 
560 
561 
562   @SuppressWarnings("unchecked")
563   protected void doTestInvokeApplication(
564     FacesContext context,
565     UIViewRoot   root,
566     UIComponent  component,
567     FacesEvent   event)
568   {
569 
570     Mock mock = createMockUIComponent();
571     UIComponent child = (UIComponent) mock.proxy();
572     // JavaServer Faces 1.0 Specification, section 2.2.5
573     // During the invoke-application phase,
574     // no per-component lifecycle methods may be called.
575 
576     // construct the UIComponent tree and
577     // execute the apply-request-values lifecycle phase
578     root.getChildren().add(component);
579     if (event != null)
580       event.queue();
581 
582     component.getChildren().add(child);
583 
584     // getRenderer called as part of UIXComponentBase.broadcast
585     Mock mockRenderkit = getMockRenderKitWrapper().getMock();
586     Mock mockRenderer = mock(Renderer.class);
587     Renderer renderer = (Renderer)mockRenderer.proxy();
588     mockRenderkit.stubs().method("getRenderer").will(returnValue(renderer));
589 
590     root.processApplication(context);
591 
592     mock.verify();
593   }
594 
595   /**
596    * Tests the render-response lifecycle phase.
597    *
598    * @throws IOException  when test fails
599    */
600   @SuppressWarnings("unchecked")
601   protected void doTestRenderResponse(
602     UIComponent component) throws IOException
603   {
604 
605 //    MockRenderKitFactory factory = setupMockRenderKitFactory();
606 
607     Mock mockRenderkit = getMockRenderKitWrapper().getMock();
608 
609     Mock mockRenderer = mock(Renderer.class);
610     Renderer renderer = (Renderer) mockRenderer.proxy();
611     mockRenderkit.stubs().method("getRenderer").will(returnValue(renderer));
612 
613     Mock mockChild = createMockUIComponent(); //mock(UIComponent.class);
614     UIComponent child = (UIComponent) mockChild.proxy();
615 
616     mockChild.expects(atLeastOnce()).method("getParent").will(returnValue(null));
617     mockChild.expects(atLeastOnce()).method("isTransient").will(returnValue(false));
618     mockChild.expects(atLeastOnce()).method("getRendersChildren").will(returnValue(true));
619 
620     UIViewRoot root = new UIViewRoot();
621 
622     mockRenderer.expects(atLeastOnce()).method("getRendersChildren").will(returnValue(false));
623     mockRenderer.expects(never()).method("decode");
624     mockRenderer.expects(never()).method("getConvertedValue");
625     mockRenderer.expects(never()).method("encodeChildren");
626 
627     if (isRendererUsed())
628     {
629       mockRenderer.expects(once()).method("encodeBegin");
630       mockRenderer.expects(once()).method("encodeEnd");
631     }
632     else
633     {
634       mockRenderer.expects(never()).method("encodeBegin");
635       mockRenderer.expects(never()).method("encodeEnd");
636     }
637 
638     // JavaServer Faces 1.0 Specification, section 2.2.6
639     // During the render-response phase,
640     // only the encodeBegin, encodeEnd, encodeChildren
641     // and processSaveState lifecycle methods may be called.
642     mockChild.expects(never()).method("processRestoreState");
643     mockChild.expects(never()).method("processDecodes");
644     mockChild.expects(never()).method("processValidators");
645     mockChild.expects(never()).method("processUpdates");
646     mockChild.expects(once()).method("processSaveState");
647 
648     //fix this!
649     mockChild.expects(once()).method("encodeBegin");
650     mockChild.expects(once()).method("encodeChildren");
651     mockChild.expects(once()).method("encodeEnd");
652 
653     root.getChildren().add(component);
654     component.getChildren().add(child);
655 
656     FacesContext current = FacesContext.getCurrentInstance();
657     try
658     {
659       TestFacesContext.setCurrentInstance(facesContext);
660       root.processSaveState(facesContext);
661       doRenderResponse(facesContext, root);
662     }
663     finally
664     {
665       TestFacesContext.setCurrentInstance(current);
666     }
667 
668     mockRenderer.verify();
669     mockChild.verify();
670   }
671 
672   protected void doTestValidateFailure(
673     UIViewRoot root)
674   {
675     // -= Simon =-
676     // All those variables do not seem to be used and do not seem
677     // to test anything either
678     /*Mock mockRenderkit = getMockRenderKitWrapper().getMock();
679     RenderKit renderkit = getMockRenderKitWrapper().getRenderKit();
680     */
681     Mock mockRenderer = mock(Renderer.class);
682     /*Renderer renderer = (Renderer) mockRenderer.proxy();
683 
684     Mock mockValidator = mock(Validator.class);
685     Validator validator = (Validator) mockValidator.proxy();
686 
687     ViewHandler viewhandler = this.facesContext.getApplication().getViewHandler();*/
688 
689     setCurrentContext(facesContext);
690 
691     root.processValidators(facesContext);
692 
693     mockRenderer.verify();
694 
695     setCurrentContext(null);
696   }
697 
698   /**
699    * Creates a MockUIComponent that does not expect to have
700    * any of its lifecycle methods invoked;  if you expect to
701    * have any invoked, override the "expected calls" for
702    * that lifecycle method.
703    */
704   protected Mock createMockUIComponent()
705   {
706     Mock mock = mock(UIComponent.class);
707 
708     mock.stubs().method("getParent").will(returnValue(null));
709     mock.stubs().method("setParent");
710     mock.stubs().method("getFacetsAndChildren").will(returnIterator(Collections.emptyList()));
711     mock.stubs().method("isRendered").will(returnValue(true));
712 
713     mock.stubs().method("getAttributes").will(returnValue(Collections.emptyMap()));
714     mock.stubs().method("pushComponentToEL").withAnyArguments();
715     mock.stubs().method("popComponentFromEL").withAnyArguments();
716 
717     mock.expects(never()).method("processRestoreState");
718     mock.expects(never()).method("processDecodes");
719     mock.expects(never()).method("processValidators");
720     mock.expects(never()).method("processUpdates");
721     mock.expects(never()).method("processSaveState");
722     mock.expects(never()).method("encodeBegin");
723     mock.expects(never()).method("encodeChildren");
724     mock.expects(never()).method("encodeEnd");
725 
726     return mock;
727   }
728 
729   protected boolean willChildrenBeProcessed(UIComponent component)
730   {
731     return (component.isRendered());
732   }
733 
734   protected boolean willChildrenBeRendered(UIComponent component)
735   {
736     return true;
737   }
738 
739   protected boolean isRendererUsed()
740   {
741     return _isRendererUsed;
742   }
743 
744   protected void setRendererUsed(boolean isRendererUsed)
745   {
746     _isRendererUsed = isRendererUsed;
747   }
748 
749   private boolean _isRendererUsed = true;
750   private MockRequestContext _mockRequestContext;
751 }