MYFACES-2492 Update and create Mock classes in myfaces-test20 (Thanks to Ingo Hofmann for this patch)
diff --git a/test12/src/main/java/org/apache/myfaces/test/config/ConfigParser.java b/test12/src/main/java/org/apache/myfaces/test/config/ConfigParser.java
index a6a201f..7bda502 100644
--- a/test12/src/main/java/org/apache/myfaces/test/config/ConfigParser.java
+++ b/test12/src/main/java/org/apache/myfaces/test/config/ConfigParser.java
@@ -17,19 +17,20 @@
 
 package org.apache.myfaces.test.config;
 
-import java.io.IOException;
-import java.net.URL;
+import org.apache.commons.digester.Digester;
+import org.apache.commons.digester.Rule;
+import org.apache.myfaces.test.mock.MockRenderKit;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
 import javax.faces.FactoryFinder;
 import javax.faces.application.Application;
 import javax.faces.application.ApplicationFactory;
 import javax.faces.render.RenderKit;
 import javax.faces.render.RenderKitFactory;
 import javax.faces.render.Renderer;
-import org.apache.commons.digester.Digester;
-import org.apache.commons.digester.Rule;
-import org.apache.myfaces.test.mock.MockRenderKit;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
+import java.io.IOException;
+import java.net.URL;
 
 /**
  * <p>Utility class to parse JavaServer Faces configuration resources, and
@@ -55,7 +56,7 @@
  *
  * @since 1.1
  */
-public final class ConfigParser {
+public class ConfigParser {
     
 
     // ------------------------------------------------------------ Constructors
diff --git a/test12/src/main/java/org/apache/myfaces/test/mock/MockFacesContext.java b/test12/src/main/java/org/apache/myfaces/test/mock/MockFacesContext.java
index af90e78..eff4f9f 100644
--- a/test12/src/main/java/org/apache/myfaces/test/mock/MockFacesContext.java
+++ b/test12/src/main/java/org/apache/myfaces/test/mock/MockFacesContext.java
@@ -112,7 +112,7 @@
     private Application application = null;

     private ExternalContext externalContext = null;

     private Lifecycle lifecycle = null;

-    private Map messages = new HashMap();

+    protected Map messages = new HashMap(); // needs to be accessed in subclass MockFacesContext20

     private boolean renderResponse = false;

     private boolean responseComplete = false;

     private ResponseStream responseStream = null;

diff --git a/test20/src/main/java/org/apache/myfaces/test/config/ConfigParser20.java b/test20/src/main/java/org/apache/myfaces/test/config/ConfigParser20.java
new file mode 100644
index 0000000..8325508
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/config/ConfigParser20.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.myfaces.test.config;
+
+import org.apache.commons.digester.Digester;
+import org.apache.commons.digester.Rule;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import javax.faces.FactoryFinder;
+import javax.faces.application.Application;
+import javax.faces.application.ApplicationFactory;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * <p>Extended {@link org.apache.myfaces.test.config.ConfigParser ConfigParser} to cover JSF 2.0 artifacts</p>
+ * <p/>
+ * <p>The following artifacts are registered additionally:</p>
+ * <ul>
+ * <li><code>Behavior</code></li>
+ * </ul>
+ *
+ * @since 2.0
+ */
+public class ConfigParser20 extends ConfigParser
+{
+
+    /**
+     * Creates a new instance of ConfigParser
+     */
+    public ConfigParser20()
+    {
+        super();
+    }
+
+    /**
+     * <p>The <code>Digester</code> instance we will use for parsing.</p>
+     */
+    private Digester digester = null;
+
+    @Override
+    public void parse(URL url) throws IOException, SAXException
+    {
+
+        super.parse(url);
+
+        // Acquire and configure the Digester instance we will use
+        Digester digester = digester();
+        ApplicationFactory factory = (ApplicationFactory)
+            FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
+        Application application = factory.getApplication();
+        digester.push(application);
+
+        // Perform the required parsing
+        try {
+            digester.parse(url);
+        }
+        finally {
+            digester.clear();
+        }
+
+    }
+
+
+    @Override
+    public void parse(URL[] urls) throws IOException, SAXException
+    {
+
+        for (URL url : urls) {
+            parse(url);
+        }
+    }
+
+
+    /**
+     * @return Return the <code>Digester</code> instance we will use for parsing,
+     *         creating and configuring a new instance if necessary.
+     */
+    private Digester digester()
+    {
+
+        if (this.digester == null) {
+            this.digester = new Digester();
+
+            digester.addRule("faces-config/behavior", new BehaviorRule());
+            digester.addCallMethod
+                ("faces-config/behavior/behavior-id", "setBehaviorId", 0);
+            digester.addCallMethod
+                ("faces-config/behavior/behavior-class", "setBehaviorClass", 0);
+        }
+        return this.digester;
+
+    }
+
+    /**
+     * <p>Data bean that stores information related to a behavior.</p>
+     */
+    class BehaviorBean
+    {
+
+        private String behaviorClass;
+
+        public String getBehaviorClass()
+        {
+            return this.behaviorClass;
+        }
+
+        @SuppressWarnings("unused")
+        public void setBehaviorClass(String behaviorClass)
+        {
+            this.behaviorClass = behaviorClass;
+        }
+
+        private String behaviorId;
+
+        public String getBehaviorId()
+        {
+            return this.behaviorId;
+        }
+
+        @SuppressWarnings("unused")
+        public void setBehaviorId(String behaviorId)
+        {
+            this.behaviorId = behaviorId;
+        }
+
+    }
+
+
+    /**
+     * <p>Digester <code>Rule</code> for processing behaviors.</p>
+     */
+    class BehaviorRule extends Rule
+    {
+
+        public void begin(String namespace, String name, Attributes attributes)
+        {
+            getDigester().push(new BehaviorBean());
+        }
+
+        public void end(String namespace, String name)
+        {
+            BehaviorBean bean = (BehaviorBean) getDigester().pop();
+            Application application = (Application) getDigester().peek();
+            application.addBehavior(bean.getBehaviorId(), bean.getBehaviorClass());
+        }
+
+    }
+}
diff --git a/test20/src/main/java/org/apache/myfaces/test/config/MyBehavior.java b/test20/src/main/java/org/apache/myfaces/test/config/MyBehavior.java
new file mode 100644
index 0000000..c5f113a
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/config/MyBehavior.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.myfaces.test.config;
+
+import javax.faces.component.behavior.Behavior;
+import javax.faces.event.BehaviorEvent;
+
+public class MyBehavior implements Behavior
+{
+    public void broadcast(BehaviorEvent event)
+    {
+        
+    }
+}
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/MockApplication20.java b/test20/src/main/java/org/apache/myfaces/test/mock/MockApplication20.java
index 1c5e3bd..b1d6080 100644
--- a/test20/src/main/java/org/apache/myfaces/test/mock/MockApplication20.java
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/MockApplication20.java
@@ -17,31 +17,31 @@
 
 package org.apache.myfaces.test.mock;
 
-import java.lang.reflect.Constructor;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.logging.Level;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 
 import javax.faces.FacesException;
 import javax.faces.application.ProjectStage;
+import javax.faces.application.ResourceHandler;
+import javax.faces.component.behavior.Behavior;
 import javax.faces.context.FacesContext;
 import javax.faces.event.AbortProcessingException;
 import javax.faces.event.SystemEvent;
 import javax.faces.event.SystemEventListener;
 import javax.faces.event.SystemEventListenerHolder;
-import javax.naming.Context;
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import java.lang.reflect.Constructor;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 public class MockApplication20 extends MockApplication12
 {
 
+
     // ------------------------------------------------------------ Constructors
 
     public MockApplication20()
@@ -185,16 +185,18 @@
     
     private ProjectStage _projectStage;
 
+    private final Map<String, Class<?>> _behaviorClassMap = new ConcurrentHashMap<String, Class<?>>();
+
+    private final Map<String, Class<?>> _validatorClassMap = new ConcurrentHashMap<String, Class<?>>();
+
+    private ResourceHandler _resourceHandler;
+
     // ----------------------------------------------------- Mock Object Methods
     
-    /**
-     * TODO: Implement this one correctly
-     */
-    @Override
     public Map<String, String> getDefaultValidatorInfo()
     {
-        return _defaultValidatorsIds;
-    }    
+        return Collections.unmodifiableMap(_defaultValidatorsIds);
+    }
 
     private static SystemEvent _traverseListenerList(
             List<? extends SystemEventListener> listeners,
@@ -267,8 +269,7 @@
             throw new NullPointerException("String " + paramName + " cannot be empty.");
         }
     }
-    
-    @Override
+
     public void publishEvent(FacesContext facesContext, Class<? extends SystemEvent> systemEventClass, Class<?> sourceBaseType, Object source)
     {
         checkNull(systemEventClass, "systemEventClass");
@@ -303,65 +304,20 @@
         }
     }
 
-    @Override
     public void publishEvent(FacesContext facesContext, Class<? extends SystemEvent> systemEventClass, Object source)
     {
         publishEvent(facesContext, systemEventClass, source.getClass(), source);
     }
-    
-    @Override
+
     public ProjectStage getProjectStage()
     {
         // If the value has already been determined by a previous call to this
         // method, simply return that value.
         if (_projectStage == null)
         {
-            /*
-            String stageName = null;
-            // Look for a JNDI environment entry under the key given by the
-            // value of
-            // ProjectStage.PROJECT_STAGE_JNDI_NAME (return type of
-            // java.lang.String).
-            try
-            {
-                Context ctx = new InitialContext();
-                Object temp = ctx.lookup(ProjectStage.PROJECT_STAGE_JNDI_NAME);
-                if (temp != null)
-                {
-                    if (temp instanceof String)
-                    {
-                        stageName = (String) temp;
-                    }
-                    else
-                    {
-                        log.severe("JNDI lookup for key " + ProjectStage.PROJECT_STAGE_JNDI_NAME
-                                + " should return a java.lang.String value");
-                    }
-                }
-            }
-            catch (NamingException e)
-            {
-                // no-op
-            }*/
 
-            /*
-             * If found, continue with the algorithm below, otherwise, look for an entry in the initParamMap of the
-             * ExternalContext from the current FacesContext with the key ProjectStage.PROJECT_STAGE_PARAM_NAME
-             */
-            
-            //if (stageName == null)
-            //{
-                FacesContext context = FacesContext.getCurrentInstance();
-                String stageName = context.getExternalContext().getInitParameter(ProjectStage.PROJECT_STAGE_PARAM_NAME);
-            //}
-
-            /*
-             * If not found so far, let's try the Apache MyFaces extension (see MYFACES-2235)
-             */
-            //if (stageName == null)
-            //{
-            //    stageName = System.getProperty(MYFACES_PROJECT_STAGE_SYSTEM_PROPERTY_NAME);
-            //}
+            FacesContext context = FacesContext.getCurrentInstance();
+            String stageName = context.getExternalContext().getInitParameter(ProjectStage.PROJECT_STAGE_PARAM_NAME);
 
             // If a value is found found
             if (stageName != null)
@@ -380,25 +336,129 @@
                     //log.log(Level.SEVERE, "Couldn't discover the current project stage", e);
                 }
             }
-            else
-            {
-                //if (log.isLoggable(Level.INFO))
-                //{
-                //    log.info("Couldn't discover the current project stage, using " + ProjectStage.Production);
-                //}
-            }
-
-            /*
-             * If not found, or any of the previous attempts to discover the enum constant value have failed, log a
-             * descriptive error message, assign the value as ProjectStage.Production and return it.
-             */
-
+            
             _projectStage = ProjectStage.Production;
         }
 
         return _projectStage;
     }
-        
-    // ------------------------------------------------- ExternalContext Methods
 
+
+    public void addBehavior(String behaviorId, String behaviorClass)
+    {
+        checkNull(behaviorId, "behaviorId");
+        checkEmpty(behaviorId, "behaviorId");
+        checkNull(behaviorClass, "behaviorClass");
+        checkEmpty(behaviorClass, "behaviorClass");
+
+        try {
+            _behaviorClassMap.put(behaviorId, Class.forName(behaviorClass));
+        } catch (ClassNotFoundException ignore) {
+
+        }
+
+    }
+
+    public Iterator<String> getBehaviorIds()
+    {
+        return _behaviorClassMap.keySet().iterator();
+    }
+
+    public Behavior createBehavior(String behaviorId) throws FacesException
+    {
+        checkNull(behaviorId, "behaviorId");
+        checkEmpty(behaviorId, "behaviorId");
+
+        final Class<?> behaviorClass = this._behaviorClassMap.get(behaviorId);
+        if (behaviorClass == null)
+        {
+            throw new FacesException("Could not find any registered behavior-class for behaviorId : " + behaviorId);
+        }
+
+        try
+        {
+            final Behavior behavior = (Behavior) behaviorClass.newInstance();
+            return behavior;
+        }
+        catch (Exception e)
+        {
+            throw new FacesException("Could not instantiate behavior: " + behaviorClass, e);
+        }
+    }
+
+    @Override
+    public void addValidator(String validatorId, String validatorClass) {
+        super.addValidator(validatorId, validatorClass);
+
+        try {
+        _validatorClassMap.put(validatorId,
+                Class.forName(validatorClass));
+        } catch (ClassNotFoundException ex) {
+            throw new FacesException(ex.getMessage());
+        }
+
+    }
+
+    public void addDefaultValidatorId(String validatorId)
+    {
+        if (_validatorClassMap.containsKey(validatorId))
+        {
+            _defaultValidatorsIds.put(validatorId, _validatorClassMap.get(validatorId).getName());
+        }
+    }
+
+    public final ResourceHandler getResourceHandler()
+    {
+        return _resourceHandler;
+    }
+
+    public final void setResourceHandler(ResourceHandler resourceHandler)
+    {
+        checkNull(resourceHandler, "resourceHandler");
+
+        _resourceHandler = resourceHandler;
+    }
+
+    public void subscribeToEvent(Class<? extends SystemEvent> systemEventClass, SystemEventListener listener)
+    {
+        subscribeToEvent(systemEventClass, null, listener);
+    }
+
+    public void subscribeToEvent(Class<? extends SystemEvent> systemEventClass, Class<?> sourceClass,
+                                 SystemEventListener listener)
+    {
+        checkNull(systemEventClass, "systemEventClass");
+        checkNull(listener, "listener");
+
+        SystemListenerEntry systemListenerEntry;
+        synchronized (_systemEventListenerClassMap)
+        {
+            systemListenerEntry = _systemEventListenerClassMap.get(systemEventClass);
+            if (systemListenerEntry == null)
+            {
+                systemListenerEntry = new SystemListenerEntry();
+                _systemEventListenerClassMap.put(systemEventClass, systemListenerEntry);
+            }
+        }
+
+        systemListenerEntry.addListener(listener, sourceClass);
+    }
+
+    public void unsubscribeFromEvent(Class<? extends SystemEvent> systemEventClass, SystemEventListener listener)
+    {
+        unsubscribeFromEvent(systemEventClass, null, listener);
+    }
+
+    public void unsubscribeFromEvent(Class<? extends SystemEvent> systemEventClass, Class<?> sourceClass,
+                                     SystemEventListener listener)
+    {
+        checkNull(systemEventClass, "systemEventClass");
+        checkNull(listener, "listener");
+
+        SystemListenerEntry systemListenerEntry = _systemEventListenerClassMap.get(systemEventClass);
+        if (systemListenerEntry != null)
+        {
+            systemListenerEntry.removeListener(listener, sourceClass);
+        }
+    }
 }
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandler.java b/test20/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandler.java
new file mode 100644
index 0000000..8d89d4d
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandler.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import javax.el.ELException;
+import javax.faces.FacesException;
+import javax.faces.context.ExceptionHandler;
+import javax.faces.event.AbortProcessingException;
+import javax.faces.event.ExceptionQueuedEvent;
+import javax.faces.event.ExceptionQueuedEventContext;
+import javax.faces.event.SystemEvent;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * <p>Mock implementation of <code>ExceptionHandler</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 2.0
+ */
+public class MockExceptionHandler extends ExceptionHandler
+{
+
+    private Queue<ExceptionQueuedEvent> handled;
+    private Queue<ExceptionQueuedEvent> unhandled;
+    private ExceptionQueuedEvent handledAndThrown;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ExceptionQueuedEvent getHandledExceptionQueuedEvent()
+    {
+        return handledAndThrown;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Iterable<ExceptionQueuedEvent> getHandledExceptionQueuedEvents()
+    {
+        return handled == null ? Collections.<ExceptionQueuedEvent>emptyList() : handled;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Throwable getRootCause(Throwable t)
+    {
+        if (t == null) {
+            throw new NullPointerException("t");
+        }
+
+        while (t != null) {
+            Class<?> clazz = t.getClass();
+            if (!clazz.equals(FacesException.class) && !clazz.equals(ELException.class)) {
+                return t;
+            }
+
+            t = t.getCause();
+        }
+
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Iterable<ExceptionQueuedEvent> getUnhandledExceptionQueuedEvents()
+    {
+        return unhandled == null ? Collections.<ExceptionQueuedEvent>emptyList() : unhandled;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handle() throws FacesException
+    {
+        if (unhandled != null && !unhandled.isEmpty()) {
+            if (handled == null) {
+                handled = new LinkedList<ExceptionQueuedEvent>();
+            }
+
+            FacesException toThrow = null;
+
+            do {
+                // For each ExceptionEvent in the list
+
+                // get the event to handle
+                ExceptionQueuedEvent event = unhandled.peek();
+                try {
+                    // call its getContext() method
+                    ExceptionQueuedEventContext context = event.getContext();
+
+                    // and call getException() on the returned result
+                    Throwable exception = context.getException();
+
+                    // Upon encountering the first such Exception that is not an instance of
+                    // javax.faces.event.AbortProcessingException
+                    if (!shouldSkip(exception)) {
+                        // set handledAndThrown so that getHandledExceptionQueuedEvent() returns this event
+                        handledAndThrown = event;
+
+                        // Re-wrap toThrow in a ServletException or (PortletException, if in a portlet environment)
+                        // and throw it
+                        // FIXME: The spec says to NOT use a FacesException to propagate the exception, but I see
+                        //        no other way as ServletException is not a RuntimeException
+                        toThrow = wrap(getRethrownException(exception));
+                        break;
+                    }
+                }
+                catch (Throwable t) {
+                    // A FacesException must be thrown if a problem occurs while performing
+                    // the algorithm to handle the exception
+                    throw new FacesException("Could not perform the algorithm to handle the Exception", t);
+                }
+                finally {
+                    // if we will throw the Exception or if we just logged it,
+                    // we handled it in either way --> add to handled
+                    handled.add(event);
+                    unhandled.remove(event);
+                }
+            }
+            while (!unhandled.isEmpty());
+
+            // do we have to throw an Exception?
+            if (toThrow != null) {
+                throw toThrow;
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isListenerForSource(Object source)
+    {
+        return source instanceof ExceptionQueuedEventContext;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void processEvent(SystemEvent exceptionQueuedEvent) throws AbortProcessingException
+    {
+        if (unhandled == null) {
+            unhandled = new LinkedList<ExceptionQueuedEvent>();
+        }
+
+        unhandled.add((ExceptionQueuedEvent) exceptionQueuedEvent);
+    }
+
+    protected Throwable getRethrownException(Throwable exception)
+    {
+        // Let toRethrow be either the result of calling getRootCause() on the Exception,
+        // or the Exception itself, whichever is non-null
+        Throwable toRethrow = getRootCause(exception);
+        if (toRethrow == null) {
+            toRethrow = exception;
+        }
+
+        return toRethrow;
+    }
+
+    protected FacesException wrap(Throwable exception)
+    {
+        if (exception instanceof FacesException) {
+            return (FacesException) exception;
+        }
+        return new FacesException(exception);
+    }
+
+    protected boolean shouldSkip(Throwable exception)
+    {
+        return exception instanceof AbortProcessingException;
+    }
+}
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandlerFactory.java b/test20/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandlerFactory.java
new file mode 100644
index 0000000..590c540
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/MockExceptionHandlerFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import javax.faces.context.ExceptionHandler;
+import javax.faces.context.ExceptionHandlerFactory;
+
+/**
+ * <p>Mock implementation of <code>ExceptionHandlerFactory</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 2.0
+ */
+public class MockExceptionHandlerFactory extends ExceptionHandlerFactory
+{
+
+    @Override
+    public ExceptionHandler getExceptionHandler()
+    {
+        return new MockExceptionHandler();
+    }
+}
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/MockExternalContext20.java b/test20/src/main/java/org/apache/myfaces/test/mock/MockExternalContext20.java
index fde1bf3..71e3ddc 100644
--- a/test20/src/main/java/org/apache/myfaces/test/mock/MockExternalContext20.java
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/MockExternalContext20.java
@@ -17,17 +17,20 @@
 
 package org.apache.myfaces.test.mock;
 
+import javax.faces.context.Flash;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
+import java.io.Writer;
 import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import javax.servlet.ServletContext;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
 public class MockExternalContext20 extends MockExternalContext12
 {
 
@@ -39,7 +42,6 @@
         super(context, request, response);
     }
 
-    @Override
     public String getMimeType(String file)
     {
         return context.getMimeType(file);
@@ -56,7 +58,6 @@
 
     // ------------------------------------------------- ExternalContext Methods
 
-    @Override
     public String encodeBookmarkableURL(String baseUrl, Map<String,List<String>> parameters)
     {
         return response.encodeURL(encodeURL(baseUrl, parameters));
@@ -150,5 +151,66 @@
         }
 
         return newUrl.toString();
-    }    
+    }
+
+    public String encodeRedirectURL(String baseUrl, Map<String,List<String>> parameters)
+    {
+        return response.encodeRedirectURL(encodeURL(baseUrl, parameters));
+    }
+
+    public String getContextName() {
+        return context.getServletContextName();
+    }
+
+    public String getRealPath(String path)
+    {
+        return context.getRealPath(path);
+    }
+
+    public void responseSendError(int statusCode, String message) throws IOException
+    {
+        if (message == null)
+        {
+            response.sendError(statusCode);
+        }
+        else
+        {
+            response.sendError(statusCode, message);
+        }
+    }
+
+    public void setResponseHeader(String name, String value)
+    {
+        response.setHeader(name, value);
+    }
+
+    public String getRequestScheme()
+    {
+        return request.getScheme();
+    }
+
+    public String getRequestServerName()
+    {
+        return request.getServerName();
+    }
+
+    public int getRequestServerPort()
+    {
+        return request.getServerPort();
+    }
+
+    public OutputStream getResponseOutputStream() throws IOException
+    {
+        return response.getOutputStream();
+    }
+
+    public Writer getResponseOutputWriter() throws IOException
+    {
+        return response.getWriter();
+    }
+
+    public Flash getFlash()
+    {
+        return MockFlash.getCurrentInstance(this);
+    }
 }
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/MockFacesContext20.java b/test20/src/main/java/org/apache/myfaces/test/mock/MockFacesContext20.java
index 7bb43b1..062e3f0 100644
--- a/test20/src/main/java/org/apache/myfaces/test/mock/MockFacesContext20.java
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/MockFacesContext20.java
@@ -17,10 +17,15 @@
 
 package org.apache.myfaces.test.mock;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.faces.FactoryFinder;
+import javax.faces.application.FacesMessage;
+import javax.faces.context.ExceptionHandler;
 import javax.faces.context.ExternalContext;
 import javax.faces.context.PartialViewContext;
 import javax.faces.context.PartialViewContextFactory;
@@ -30,8 +35,6 @@
 public class MockFacesContext20 extends MockFacesContext12 {
 
     // ------------------------------------------------------------ Constructors
-
-    private boolean _processingEvents = true;
     
     public MockFacesContext20() {
         super();
@@ -48,26 +51,27 @@
         super(externalContext, lifecycle);
     }
 
-    // ----------------------------------------------------- Mock Object Methods
-
     // ------------------------------------------------------ Instance Variables
 
-    // ----------------------------------------------------- Mock Object Methods
+    private boolean _processingEvents = true;
+    private ExceptionHandler _exceptionHandler = null;
+    private PhaseId _currentPhaseId = PhaseId.RESTORE_VIEW;
+    private boolean _postback;
+    private PartialViewContext _partialViewContext = null;
+    private Map<Object,Object> attributes;
+    private boolean _validationFailed = false;
 
-    private boolean postback;
+    // ----------------------------------------------------- Mock Object Methods   
 
-    @Override
     public boolean isPostback()
     {
-        return postback;
+        return _postback;
     }
     
     public void setPostback(boolean value)
     {
-        postback = value;
+        _postback = value;
     }
-
-    private PhaseId _currentPhaseId = PhaseId.RESTORE_VIEW;
     
     public PhaseId getCurrentPhaseId()
     {
@@ -78,10 +82,7 @@
     {
         this._currentPhaseId = _currentPhaseId;
     }
-    
-    private Map<Object,Object> attributes;
 
-    @Override
     public Map<Object, Object> getAttributes()
     {
         if (attributes == null)
@@ -90,10 +91,7 @@
         }
         return attributes;
     }
-    
-    private PartialViewContext _partialViewContext = null;
-    
-    @Override
+
     public PartialViewContext getPartialViewContext()
     {
         if (_partialViewContext == null)
@@ -111,12 +109,59 @@
         return _processingEvents;
     }
     
-    @Override
     public void setProcessingEvents(boolean processingEvents)
     {
         _processingEvents = processingEvents;
     }
-    
+
+    public ExceptionHandler getExceptionHandler()
+    {
+        return _exceptionHandler;
+    }
+
+    public void setExceptionHandler(ExceptionHandler exceptionHandler)
+    {
+        _exceptionHandler = exceptionHandler;
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<FacesMessage> getMessageList()
+    {
+        if (messages == null)
+        {
+            return Collections.unmodifiableList(Collections.<FacesMessage>emptyList());
+        }
+
+        List<FacesMessage> lst = new ArrayList<FacesMessage>();
+        for(List<FacesMessage> curLst : ((Map<String, List<FacesMessage>>) messages).values())
+        {
+            lst.addAll(curLst);
+        }
+
+        return Collections.unmodifiableList(lst);
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<FacesMessage> getMessageList(String clientId)
+    {
+        if (messages == null || !messages.containsKey(clientId))
+        {
+            return Collections.unmodifiableList(Collections.<FacesMessage>emptyList());
+        }
+
+        return ((Map<String, List<FacesMessage>>) messages).get(clientId);
+    }
+
+    public boolean isValidationFailed()
+    {
+        return _validationFailed;
+    }
+
+    public void validationFailed()
+    {
+        _validationFailed = true;
+    }
+
     // ------------------------------------------------- ExternalContext Methods
 
 }
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/MockFlash.java b/test20/src/main/java/org/apache/myfaces/test/mock/MockFlash.java
new file mode 100644
index 0000000..586473f
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/MockFlash.java
@@ -0,0 +1,733 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import javax.faces.application.FacesMessage;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import javax.faces.context.Flash;
+import javax.faces.event.PhaseId;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * <p>Mock implementation of <code>Flash</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 2.0
+ */
+public class MockFlash extends Flash
+{
+
+    /**
+     * Key on app map to keep current instance
+     */
+    static protected final String FLASH_INSTANCE = MockFlash.class.getName()
+        + ".INSTANCE";
+
+    /**
+     * Key used to check if there is the current request will be or was redirected
+     */
+    static protected final String FLASH_REDIRECT = MockFlash.class.getName()
+        + ".REDIRECT";
+
+    /**
+     * Key used to check if this request should keep messages (like tomahawk sandbox RedirectTracker.
+     * Used when post-redirect-get pattern is used)
+     */
+    static protected final String FLASH_KEEP_MESSAGES = MockFlash.class.getName()
+        + ".KEEP_MESSAGES";
+
+    static protected final String FLASH_KEEP_MESSAGES_LIST = "KEEPMESSAGESLIST";
+
+    /**
+     * Session map prefix to flash maps
+     */
+    static protected final String FLASH_SCOPE_CACHE = MockFlash.class.getName()
+        + ".SCOPE";
+
+    static protected final String FLASH_CURRENT_MAP_CACHE = MockFlash.class.getName()
+        + ".CURRENTMAP.CACHE";
+
+    static protected final String FLASH_CURRENT_MAP_KEY = MockFlash.class.getName()
+        + ".CURRENTMAP.KEY";
+
+    static protected final String FLASH_POSTBACK_MAP_CACHE = MockFlash.class.getName()
+        + ".POSTBACKMAP.CACHE";
+
+    static protected final String FLASH_POSTBACK_MAP_KEY = MockFlash.class.getName()
+        + ".POSTBACKMAP.KEY";
+
+    static private final char SEPARATOR_CHAR = '.';
+
+    // the current token value
+    private final AtomicLong _count;
+
+    public MockFlash()
+    {
+        _count = new AtomicLong(_getSeed());
+    }
+
+    /**
+     * @return a cryptographically secure random number to use as the _count seed
+     */
+    private static long _getSeed()
+    {
+        SecureRandom rng;
+        try {
+            // try SHA1 first
+            rng = SecureRandom.getInstance("SHA1PRNG");
+        }
+        catch (NoSuchAlgorithmException e) {
+            // SHA1 not present, so try the default (which could potentially not be
+            // cryptographically secure)
+            rng = new SecureRandom();
+        }
+
+        // use 48 bits for strength and fill them in
+        byte[] randomBytes = new byte[6];
+        rng.nextBytes(randomBytes);
+
+        // convert to a long
+        return new BigInteger(randomBytes).longValue();
+    }
+
+    /**
+     * @return the next token to be assigned to this request
+     */
+    protected String _getNextToken()
+    {
+        // atomically increment the value
+        long nextToken = _count.incrementAndGet();
+
+        // convert using base 36 because it is a fast efficient subset of base-64
+        return Long.toString(nextToken, 36);
+    }
+
+    /**
+     * Return a Flash instance from the application map
+     *
+     * @param context
+     * @return
+     */
+    public static Flash getCurrentInstance(ExternalContext context)
+    {
+        Map<String, Object> applicationMap = context.getApplicationMap();
+        Flash flash = (Flash) applicationMap.get(FLASH_INSTANCE);
+
+        synchronized (applicationMap) {
+            if (flash == null) {
+                flash = new MockFlash();
+                context.getApplicationMap().put(FLASH_INSTANCE, flash);
+            }
+        }
+        return flash;
+    }
+
+    /**
+     * Return a wrapper from the session map used to implement flash maps
+     * for more information see SubKeyMap doc
+     */
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> _getMapFromSession(FacesContext context,
+                                                   String token, boolean createIfNeeded)
+    {
+        ExternalContext external = context.getExternalContext();
+        Object session = external.getSession(true);
+
+        Map<String, Object> map = null;
+
+        // Synchronize on the session object to ensure that
+        // we don't ever create two different caches
+        synchronized (session) {
+            map = (Map<String, Object>) external.getSessionMap().get(token);
+            if ((map == null) && createIfNeeded) {
+                map = new MockSubKeyMap<Object>(context.getExternalContext()
+                    .getSessionMap(), token);
+            }
+        }
+
+        return map;
+    }
+
+    /**
+     * Return the flash map created on this traversal. This one will be sent
+     * on next request, so it will be retrieved as postback map of the next
+     * request.
+     * <p/>
+     * Note it is supposed that FLASH_CURRENT_MAP_KEY is initialized before
+     * restore view phase (see doPrePhaseActions() for details).
+     *
+     * @param context
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    protected Map<String, Object> getCurrentRequestMap(FacesContext context)
+    {
+        Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
+        Map<String, Object> map = (Map<String, Object>) requestMap.get(FLASH_CURRENT_MAP_CACHE);
+        if (map == null) {
+            String token = (String) requestMap.get(FLASH_CURRENT_MAP_KEY);
+            String fullToken = FLASH_SCOPE_CACHE + SEPARATOR_CHAR + token;
+            map = _getMapFromSession(context, fullToken, true);
+            requestMap.put(FLASH_CURRENT_MAP_CACHE, map);
+        }
+        return map;
+    }
+
+    @SuppressWarnings("unchecked")
+    protected Map<String, Object> getPostbackRequestMap(FacesContext context)
+    {
+        Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
+        Map<String, Object> map = (Map<String, Object>) requestMap.get(FLASH_POSTBACK_MAP_CACHE);
+        if (map == null) {
+            String token = (String) requestMap.get(FLASH_POSTBACK_MAP_KEY);
+            if (token == null && isRedirect()) {
+                // In post-redirect-get, request values are reset, so we need
+                // to get the postback key again.
+                token = _getPostbackMapKey(context.getExternalContext());
+            }
+            String fullToken = FLASH_SCOPE_CACHE + SEPARATOR_CHAR + token;
+            map = _getMapFromSession(context, fullToken, true);
+            requestMap.put(FLASH_POSTBACK_MAP_CACHE, map);
+        }
+        return map;
+    }
+
+    /**
+     * Get the proper map according to the current phase:
+     * <p/>
+     * Normal case:
+     * <p/>
+     * - First request, restore view phase (create a new one): current map n
+     * - First request, execute phase: Skipped
+     * - First request, render  phase: current map n
+     * <p/>
+     * Current map n saved and put as postback map n
+     * <p/>
+     * - Second request, execute phase: postback map n
+     * - Second request, render  phase: current map n+1
+     * <p/>
+     * Post Redirect Get case: Redirect is triggered by a call to setRedirect(true) from NavigationHandler
+     * or earlier using c:set tag.
+     * <p/>
+     * - First request, restore view phase (create a new one): current map n
+     * - First request, execute phase: Skipped
+     * - First request, render  phase: current map n
+     * <p/>
+     * Current map n saved and put as postback map n
+     * <p/>
+     * POST
+     * <p/>
+     * - Second request, execute phase: postback map n
+     * <p/>
+     * REDIRECT
+     * <p/>
+     * - NavigationHandler do the redirect, requestMap data lost, called Flash.setRedirect(true)
+     * <p/>
+     * Current map n saved and put as postback map n
+     * <p/>
+     * GET
+     * <p/>
+     * - Third  request, restore view phase (create a new one): current map n+1
+     * (isRedirect() should return true as javadoc says)
+     * - Third  request, execute phase: skipped
+     * - Third  request, render  phase: current map n+1
+     * <p/>
+     * In this way proper behavior is preserved even in the case of redirect, since the GET part is handled as
+     * the "render" part of the current traversal, keeping the semantic of flash object.
+     *
+     * @return
+     */
+    private Map<String, Object> getCurrentPhaseMap()
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        if (PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId()) ||
+            !facesContext.isPostback() || isRedirect()) {
+            return getCurrentRequestMap(facesContext);
+        }
+        else {
+            return getPostbackRequestMap(facesContext);
+        }
+    }
+
+    private void _removeAllChildren(FacesContext facesContext)
+    {
+        Map<String, Object> map = getPostbackRequestMap(facesContext);
+
+        // Clear everything - note that because of naming conventions,
+        // this will in fact automatically recurse through all children
+        // grandchildren etc. - which is kind of a design flaw of SubKeyMap,
+        // but one we're relying on
+        map.clear();
+    }
+
+    /**
+     *
+     */
+    @Override
+    public void doPrePhaseActions(FacesContext facesContext)
+    {
+        Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
+
+        if (PhaseId.RESTORE_VIEW.equals(facesContext.getCurrentPhaseId())) {
+            // Generate token and put on requestMap
+            // It is necessary to set this token always, because on the next request
+            // it should be possible to change postback map.
+            String currentToken = _getNextToken();
+            requestMap.put(FLASH_CURRENT_MAP_KEY, currentToken);
+
+            if (facesContext.isPostback()) {
+                //Retore token
+                String previousToken = _getPostbackMapKey(facesContext.getExternalContext());
+
+                if (previousToken != null) {
+                    requestMap.put(FLASH_POSTBACK_MAP_KEY, previousToken);
+                }
+            }
+
+            if (isKeepMessages()) {
+                restoreMessages(facesContext);
+            }
+        }
+
+        //
+        if (PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId())) {
+            // Put current map on next previous map
+            // but only if this request is not a redirect
+            if (!isRedirect()) {
+                _addPostbackMapKey(facesContext.getExternalContext());
+            }
+        }
+    }
+
+    @Override
+    public void doPostPhaseActions(FacesContext facesContext)
+    {
+        if (PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId())) {
+            //Remove previous flash from session
+            Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
+            String token = (String) requestMap.get(FLASH_POSTBACK_MAP_KEY);
+
+            if (token != null) {
+                _removeAllChildren(facesContext);
+            }
+
+            if (isKeepMessages()) {
+                saveMessages(facesContext);
+            }
+        }
+        else if (isRedirect() &&
+            (facesContext.getResponseComplete() || facesContext.getRenderResponse())) {
+            if (isKeepMessages()) {
+                saveMessages(facesContext);
+            }
+        }
+    }
+
+    private static class MessageEntry implements Serializable
+    {
+        private final Object clientId;
+        private final Object message;
+
+        public MessageEntry(Object clientId, Object message)
+        {
+            this.clientId = clientId;
+            this.message = message;
+        }
+    }
+
+    protected void saveMessages(FacesContext facesContext)
+    {
+        List<MessageEntry> messageList = null;
+
+        Iterator<String> iterClientIds = facesContext.getClientIdsWithMessages();
+        while (iterClientIds.hasNext()) {
+            String clientId = (String) iterClientIds.next();
+            Iterator<FacesMessage> iterMessages = facesContext.getMessages(clientId);
+
+            while (iterMessages.hasNext()) {
+                FacesMessage message = iterMessages.next();
+
+                if (messageList == null) {
+                    messageList = new ArrayList<MessageEntry>();
+                }
+                messageList.add(new MessageEntry(clientId, message));
+            }
+        }
+
+        if (messageList != null) {
+            if (isRedirect()) {
+                getPostbackRequestMap(facesContext).put(FLASH_KEEP_MESSAGES_LIST, messageList);
+            }
+            else {
+                getCurrentRequestMap(facesContext).put(FLASH_KEEP_MESSAGES_LIST, messageList);
+            }
+        }
+    }
+
+    protected void restoreMessages(FacesContext facesContext)
+    {
+        Map<String, Object> postbackMap = getPostbackRequestMap(facesContext);
+        List<MessageEntry> messageList = (List<MessageEntry>)
+            postbackMap.get(FLASH_KEEP_MESSAGES_LIST);
+
+        if (messageList != null) {
+            Iterator iterMessages = messageList.iterator();
+
+            while (iterMessages.hasNext()) {
+                MessageEntry message = (MessageEntry) iterMessages.next();
+                facesContext.addMessage((String) message.clientId, (FacesMessage) message.message);
+            }
+
+            postbackMap.remove(FLASH_KEEP_MESSAGES_LIST);
+        }
+    }
+
+
+    //private void _addPreviousToken
+
+    /**
+     * Retrieve the postback map key
+     */
+    private String _getPostbackMapKey(ExternalContext externalContext)
+    {
+        String token = null;
+        Object response = externalContext.getResponse();
+        if (response instanceof HttpServletResponse) {
+            //Use a cookie
+            Cookie cookie = (Cookie) externalContext.getRequestCookieMap().get(FLASH_POSTBACK_MAP_KEY);
+            if (cookie != null) {
+                token = cookie.getValue();
+            }
+        }
+        else {
+            //Use HttpSession or PortletSession object
+            Map<String, Object> sessionMap = externalContext.getSessionMap();
+            token = (String) sessionMap.get(FLASH_POSTBACK_MAP_KEY);
+        }
+        return token;
+    }
+
+    /**
+     * Take the current map key and store it as a postback key for the next request.
+     *
+     * @param externalContext
+     */
+    private void _addPostbackMapKey(ExternalContext externalContext)
+    {
+        Object response = externalContext.getResponse();
+        String token = (String) externalContext.getRequestMap().get(FLASH_CURRENT_MAP_KEY);
+
+        //Use HttpSession or PortletSession object
+        Map<String, Object> sessionMap = externalContext.getSessionMap();
+        sessionMap.put(FLASH_POSTBACK_MAP_KEY, token);
+    }
+
+
+    /**
+     * For check if there is a redirect we to take into accout this points:
+     * <p/>
+     * 1. isRedirect() could be accessed many times during the same
+     * request.
+     * 2. According to Post-Redirect-Get pattern, we cannot
+     * ensure request scope values are preserved.
+     */
+    @Override
+    public boolean isRedirect()
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        ExternalContext externalContext = facesContext.getExternalContext();
+        Map<String, Object> requestMap = externalContext.getRequestMap();
+        Boolean redirect = (Boolean) requestMap.get(FLASH_REDIRECT);
+        if (redirect == null) {
+            Object response = externalContext.getResponse();
+            if (response instanceof HttpServletResponse) {
+                // Request values are lost after a redirect. We can create a
+                // temporal cookie to pass the params between redirect calls.
+                // It is better than use HttpSession object, because this cookie
+                // is never sent by the server.
+                Cookie cookie = (Cookie) externalContext.getRequestCookieMap()
+                    .get(FLASH_REDIRECT);
+                if (cookie != null) {
+                    redirect = Boolean.TRUE;
+                    HttpServletResponse httpResponse = (HttpServletResponse) response;
+                    // A redirect happened, so it is safe to remove the cookie, setting
+                    // the maxAge to 0 seconds. The effect is we passed FLASH_REDIRECT param
+                    // to this request object
+                    cookie.setMaxAge(0);
+                    cookie.setValue(null);
+                    httpResponse.addCookie(cookie);
+                    requestMap.put(FLASH_REDIRECT, redirect);
+                }
+                else {
+                    redirect = Boolean.FALSE;
+                }
+            }
+            else {
+                // Note that on portlet world we can't create cookies,
+                // so we are forced to use the session map. Anyway,
+                // according to the Bridge implementation(for example see
+                // org.apache.myfaces.portlet.faces.bridge.BridgeImpl)
+                // session object is created at start faces request
+                Map<String, Object> sessionMap = externalContext
+                    .getSessionMap();
+                redirect = (Boolean) sessionMap.get(FLASH_REDIRECT);
+                if (redirect != null) {
+                    sessionMap.remove(FLASH_REDIRECT);
+                    requestMap.put(FLASH_REDIRECT, redirect);
+                }
+                else {
+                    redirect = Boolean.FALSE;
+                }
+            }
+        }
+        return redirect;
+    }
+
+    @Override
+    public void setRedirect(boolean redirect)
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        ExternalContext externalContext = facesContext.getExternalContext();
+        Map<String, Object> requestMap = externalContext.getRequestMap();
+
+        Boolean previousRedirect = (Boolean) requestMap.get(FLASH_REDIRECT);
+        previousRedirect = (previousRedirect == null) ? Boolean.FALSE : previousRedirect;
+
+        if (!previousRedirect.booleanValue() && redirect) {
+            // This request contains a redirect. This condition is in general
+            // triggered by a NavigationHandler. After a redirect all request scope
+            // values get lost, so in order to preserve this value we need to
+            // pass it between request. One strategy is use a cookie that is never sent
+            // to the client. Other alternative is use the session map.
+            externalContext.getSessionMap().put(FLASH_REDIRECT, redirect);
+        }
+        requestMap.put(FLASH_REDIRECT, redirect);
+    }
+
+    /**
+     * In few words take a value from request scope map and put it on current request map,
+     * so it is visible on the next request.
+     */
+    @Override
+    public void keep(String key)
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
+        Object value = requestMap.get(key);
+        getCurrentRequestMap(facesContext).put(key, value);
+    }
+
+    /**
+     * This is just an alias for request scope map.
+     */
+    @Override
+    public void putNow(String key, Object value)
+    {
+        FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put(key, value);
+    }
+
+    @Override
+    public boolean isKeepMessages()
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        ExternalContext externalContext = facesContext.getExternalContext();
+        Map<String, Object> requestMap = externalContext.getRequestMap();
+        Boolean keepMessages = (Boolean) requestMap.get(FLASH_KEEP_MESSAGES);
+        if (keepMessages == null) {
+            Object response = externalContext.getResponse();
+            if (response instanceof HttpServletResponse) {
+                // Request values are lost after a redirect. We can create a
+                // temporal cookie to pass the params between redirect calls.
+                // It is better than use HttpSession object, because this cookie
+                // is never sent by the server.
+                Cookie cookie = (Cookie) externalContext.getRequestCookieMap()
+                    .get(FLASH_KEEP_MESSAGES);
+                if (cookie != null) {
+                    keepMessages = Boolean.TRUE;
+                    HttpServletResponse httpResponse = (HttpServletResponse) response;
+                    // It is safe to remove the cookie, setting
+                    // the maxAge to 0 seconds. The effect is we passed FLASH_KEEP_MESSAGES param
+                    // to this request object
+                    cookie.setMaxAge(0);
+                    cookie.setValue(null);
+                    httpResponse.addCookie(cookie);
+                    requestMap.put(FLASH_KEEP_MESSAGES, keepMessages);
+                }
+                else {
+                    keepMessages = Boolean.FALSE;
+                }
+            }
+            else {
+                // Note that on portlet world we can't create cookies,
+                // so we are forced to use the session map. Anyway,
+                // according to the Bridge implementation(for example see
+                // org.apache.myfaces.portlet.faces.bridge.BridgeImpl)
+                // session object is created at start faces request
+                Map<String, Object> sessionMap = externalContext
+                    .getSessionMap();
+                keepMessages = (Boolean) sessionMap.get(FLASH_KEEP_MESSAGES);
+                if (keepMessages != null) {
+                    sessionMap.remove(FLASH_KEEP_MESSAGES);
+                    requestMap.put(FLASH_KEEP_MESSAGES, keepMessages);
+                }
+                else {
+                    keepMessages = Boolean.FALSE;
+                }
+            }
+        }
+        return keepMessages;
+    }
+
+    /**
+     * If this property is true, the messages should be keep for the next request, no matter
+     * if it is a normal postback case or a post-redirect-get case.
+     */
+    @Override
+    public void setKeepMessages(boolean keepMessages)
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        ExternalContext externalContext = facesContext.getExternalContext();
+        Map<String, Object> requestMap = externalContext.getRequestMap();
+
+        Boolean previousKeepMessages = (Boolean) requestMap.get(FLASH_KEEP_MESSAGES);
+        previousKeepMessages = (previousKeepMessages == null) ? Boolean.FALSE : previousKeepMessages;
+
+        if (!previousKeepMessages.booleanValue() && keepMessages) {
+            externalContext.getSessionMap().put(FLASH_KEEP_MESSAGES, keepMessages);
+        }
+        requestMap.put(FLASH_KEEP_MESSAGES, keepMessages);
+    }
+
+    public void clear()
+    {
+        getCurrentPhaseMap().clear();
+    }
+
+    public boolean containsKey(Object key)
+    {
+        return getCurrentPhaseMap().containsKey(key);
+    }
+
+    public boolean containsValue(Object value)
+    {
+        return getCurrentPhaseMap().containsValue(value);
+    }
+
+    public Set<Entry<String, Object>> entrySet()
+    {
+        return getCurrentPhaseMap().entrySet();
+    }
+
+    public Object get(Object key)
+    {
+        if (key == null) {
+            return null;
+        }
+
+        if ("keepMessages".equals(key)) {
+            return isKeepMessages();
+        }
+        else if ("redirect".equals(key)) {
+            return isRedirect();
+        }
+
+        FacesContext context = FacesContext.getCurrentInstance();
+        Map<String, Object> postbackMap = getPostbackRequestMap(context);
+        Object returnValue = null;
+
+        if (postbackMap != null) {
+            returnValue = postbackMap.get(key);
+        }
+
+        return returnValue;
+    }
+
+    public boolean isEmpty()
+    {
+        return getCurrentPhaseMap().isEmpty();
+    }
+
+    public Set<String> keySet()
+    {
+        return getCurrentPhaseMap().keySet();
+    }
+
+    public Object put(String key, Object value)
+    {
+        if (key == null) {
+            return null;
+        }
+
+        if ("keepMessages".equals(key)) {
+            Boolean booleanValue = convertToBoolean(value);
+            this.setKeepMessages(booleanValue);
+            return booleanValue;
+        }
+        else if ("redirect".equals(key)) {
+            Boolean booleanValue = convertToBoolean(value);
+            this.setRedirect(booleanValue);
+            return booleanValue;
+        }
+        else {
+            Object returnValue = getCurrentPhaseMap().put(key, value);
+            return returnValue;
+        }
+    }
+
+    private Boolean convertToBoolean(Object value)
+    {
+        Boolean booleanValue;
+        if (value instanceof Boolean) {
+            booleanValue = (Boolean) value;
+        }
+        else {
+            booleanValue = Boolean.parseBoolean(value.toString());
+        }
+        return booleanValue;
+    }
+
+    public void putAll(Map<? extends String, ? extends Object> m)
+    {
+        getCurrentPhaseMap().putAll(m);
+    }
+
+    public Object remove(Object key)
+    {
+        return getCurrentPhaseMap().remove(key);
+    }
+
+    public int size()
+    {
+        return getCurrentPhaseMap().size();
+    }
+
+    public Collection<Object> values()
+    {
+        return getCurrentPhaseMap().values();
+    }
+
+}
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/MockPartialViewContext.java b/test20/src/main/java/org/apache/myfaces/test/mock/MockPartialViewContext.java
new file mode 100644
index 0000000..3ed0ef8
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/MockPartialViewContext.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import org.apache.myfaces.test.mock.visit.MockVisitCallback;
+
+import javax.faces.component.UIComponent;
+import javax.faces.component.visit.VisitContext;
+import javax.faces.context.FacesContext;
+import javax.faces.context.PartialResponseWriter;
+import javax.faces.context.PartialViewContext;
+import javax.faces.context.ResponseWriter;
+import javax.faces.event.PhaseId;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * <p>Mock implementation of <code>PartialViewContext</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 2.0
+ */
+public class MockPartialViewContext extends PartialViewContext
+{
+
+    private static final String FACES_REQUEST = "Faces-Request";
+    private static final String PARTIAL_AJAX = "partial/ajax";
+    private static final String PARTIAL_PROCESS = "partial/process";
+    private FacesContext _facesContext = null;
+    private Boolean _ajaxRequest = null;
+    private Boolean _partialRequest = null;
+    private Boolean _renderAll = null;
+    private Collection<String> _executeClientIds = null;
+    private Collection<String> _renderClientIds = null;
+    private PartialResponseWriter _partialResponseWriter = null;
+
+    public MockPartialViewContext(FacesContext context)
+    {
+        _facesContext = context;
+    }
+
+    @Override
+    public Collection<String> getExecuteIds()
+    {
+        if (_executeClientIds == null) {
+            String executeMode = _facesContext.getExternalContext().
+                getRequestParameterMap().get(
+                PartialViewContext.PARTIAL_EXECUTE_PARAM_NAME);
+
+            if (executeMode != null && !"".equals(executeMode) &&
+                //!PartialViewContext.NO_PARTIAL_PHASE_CLIENT_IDS.equals(executeMode) &&
+                !PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS.equals(executeMode)) {
+
+                String[] clientIds = splitShortString(_replaceTabOrEnterCharactersWithSpaces(executeMode), ' ');
+
+                //The collection must be mutable
+                List<String> tempList = new ArrayList<String>();
+                for (String clientId : clientIds) {
+                    if (clientId.length() > 0) {
+                        tempList.add(clientId);
+                    }
+                }
+                _executeClientIds = tempList;
+            }
+            else {
+                _executeClientIds = new ArrayList<String>();
+            }
+        }
+        return _executeClientIds;
+    }
+
+    @Override
+    public PartialResponseWriter getPartialResponseWriter()
+    {
+        if (_partialResponseWriter == null) {
+            ResponseWriter responseWriter = _facesContext.getResponseWriter();
+            if (responseWriter == null) {
+                // This case happens when getPartialResponseWriter() is called before
+                // render phase, like in ExternalContext.redirect(). We have to create a
+                // ResponseWriter from the RenderKit and then wrap if necessary.
+                try {
+                    responseWriter = _facesContext.getRenderKit().createResponseWriter(
+                        _facesContext.getExternalContext().getResponseOutputWriter(), "text/xml",
+                        _facesContext.getExternalContext().getRequestCharacterEncoding());
+                }
+                catch (IOException e) {
+                    throw new IllegalStateException("Cannot create Partial Response Writer", e);
+                }
+            }
+            // It is possible that the RenderKit return a PartialResponseWriter instance when
+            // createResponseWriter,  so we should cast here for it and prevent double wrapping.
+            if (responseWriter instanceof PartialResponseWriter) {
+                _partialResponseWriter = (PartialResponseWriter) responseWriter;
+            }
+            else {
+                _partialResponseWriter = new PartialResponseWriter(responseWriter);
+            }
+        }
+        return _partialResponseWriter;
+    }
+
+    @Override
+    public Collection<String> getRenderIds()
+    {
+        if (_renderClientIds == null) {
+            String renderMode = _facesContext.getExternalContext().
+                getRequestParameterMap().get(
+                PartialViewContext.PARTIAL_RENDER_PARAM_NAME);
+
+            if (renderMode != null && !"".equals(renderMode) &&
+                //!PartialViewContext.NO_PARTIAL_PHASE_CLIENT_IDS.equals(renderMode) &&
+                !PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS.equals(renderMode)) {
+                String[] clientIds = splitShortString(_replaceTabOrEnterCharactersWithSpaces(renderMode), ' ');
+
+                //The collection must be mutable
+                List<String> tempList = new ArrayList<String>();
+                for (String clientId : clientIds) {
+                    if (clientId.length() > 0) {
+                        tempList.add(clientId);
+                    }
+                }
+                _renderClientIds = tempList;
+            }
+            else {
+                _renderClientIds = new ArrayList<String>();
+            }
+        }
+        return _renderClientIds;
+    }
+
+    @Override
+    public boolean isAjaxRequest()
+    {
+        if (_ajaxRequest == null) {
+            String requestType = _facesContext.getExternalContext().
+                getRequestHeaderMap().get(FACES_REQUEST);
+            _ajaxRequest = (requestType != null && PARTIAL_AJAX.equals(requestType));
+        }
+        return _ajaxRequest;
+    }
+
+    @Override
+    public boolean isExecuteAll()
+    {
+        if (isAjaxRequest()) {
+            String executeMode = _facesContext.getExternalContext().
+                getRequestParameterMap().get(
+                PartialViewContext.PARTIAL_EXECUTE_PARAM_NAME);
+            if (PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS.equals(executeMode)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isPartialRequest()
+    {
+        if (_partialRequest == null) {
+            String requestType = _facesContext.getExternalContext().
+                getRequestHeaderMap().get(FACES_REQUEST);
+            _partialRequest = (requestType != null && PARTIAL_PROCESS.equals(requestType));
+        }
+        return isAjaxRequest() || _partialRequest;
+    }
+
+    @Override
+    public boolean isRenderAll()
+    {
+        if (_renderAll == null) {
+            if (isAjaxRequest()) {
+                String executeMode = _facesContext.getExternalContext().
+                    getRequestParameterMap().get(
+                    PartialViewContext.PARTIAL_RENDER_PARAM_NAME);
+                if (PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS.equals(executeMode)) {
+                    _renderAll = true;
+                }
+            }
+            if (_renderAll == null) {
+                _renderAll = false;
+            }
+        }
+        return _renderAll;
+    }
+
+    @Override
+    public void processPartial(PhaseId phaseId)
+    {
+        UIComponent viewRoot = _facesContext.getViewRoot();
+        VisitContext visitCtx = VisitContext.createVisitContext(_facesContext, null, null);
+        viewRoot.visitTree(visitCtx, new MockVisitCallback());
+    }
+
+    @Override
+    public void release()
+    {
+        _executeClientIds = null;
+        _renderClientIds = null;
+        _ajaxRequest = null;
+        _partialRequest = null;
+        _renderAll = null;
+        _facesContext = null;
+    }
+
+    @Override
+    public void setPartialRequest(boolean isPartialRequest)
+    {
+        _partialRequest = isPartialRequest;
+    }
+
+    @Override
+    public void setRenderAll(boolean renderAll)
+    {
+        _renderAll = renderAll;
+    }
+
+    private static String[] splitShortString(String str, char separator)
+    {
+        int len = str.length();
+
+        int lastTokenIndex = 0;
+
+        // Step 1: how many substrings?
+        //      We exchange double scan time for less memory allocation
+        for (int pos = str.indexOf(separator);
+             pos >= 0; pos = str.indexOf(separator, pos + 1)) {
+            lastTokenIndex++;
+        }
+
+        // Step 2: allocate exact size array
+        String[] list = new String[lastTokenIndex + 1];
+
+        int oldPos = 0;
+
+        // Step 3: retrieve substrings
+        for (
+            int pos = str.indexOf(separator), i = 0; pos >= 0;
+            pos = str.indexOf(separator, (oldPos = (pos + 1)))) {
+            list[i++] = str.substring(oldPos, pos);
+        }
+
+        list[lastTokenIndex] = str.substring(oldPos, len);
+
+        return list;
+    }
+
+    private String _replaceTabOrEnterCharactersWithSpaces(String mode)
+    {
+        StringBuilder builder = new StringBuilder(mode.length());
+        for (int i = 0; i < mode.length(); i++) {
+            if (mode.charAt(i) == '\t' ||
+                mode.charAt(i) == '\n') {
+                builder.append(' ');
+            }
+            else {
+                builder.append(mode.charAt(i));
+            }
+        }
+        return builder.toString();
+    }
+}
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/MockPartialViewContextFactory.java b/test20/src/main/java/org/apache/myfaces/test/mock/MockPartialViewContextFactory.java
new file mode 100644
index 0000000..9dea486
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/MockPartialViewContextFactory.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import javax.faces.context.FacesContext;
+import javax.faces.context.PartialViewContext;
+import javax.faces.context.PartialViewContextFactory;
+
+/**
+ * <p>Mock implementation of <code>PartialViewContextFactory</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 2.0
+ */
+public class MockPartialViewContextFactory extends PartialViewContextFactory
+{
+
+    @Override
+    public PartialViewContext getPartialViewContext(FacesContext context)
+    {
+        return new MockPartialViewContext(context);
+    }
+}
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/MockSubKeyMap.java b/test20/src/main/java/org/apache/myfaces/test/mock/MockSubKeyMap.java
new file mode 100644
index 0000000..28d240e
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/MockSubKeyMap.java
@@ -0,0 +1,285 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import java.util.*;
+
+/**
+ * NOTE: Class copied from trinidad to be used on MockFlash.
+ * <p/>
+ * Map that wraps another to provide an isolated namespace using
+ * a prefix.  This is especially handy for storing properties on
+ * the session in a structured manner without putting them into
+ * a true "Map" - because storing in a Map breaks session failover.
+ * (Session failover won't trigger on mutations of contained objects.)
+ * <p/>
+ * Note that there is a potential design flaw;  if you create a SubKeyMap
+ * for "mypackage.foo" and for "mypackage.foo.bar", all the keys in the
+ * latter will actually show up in the former (prefixed by ".bar").  This
+ * "flaw" is actually relied on by PageFlowScopeMap (since it provides
+ * a handy way to clear out all descendents), so don't "fix" it!
+ */
+final class MockSubKeyMap<V> extends AbstractMap<String, V>
+{
+    public MockSubKeyMap(Map<String, Object> base, String prefix)
+    {
+        if (base == null) {
+            throw new NullPointerException();
+        }
+        if (prefix == null) {
+            throw new NullPointerException();
+        }
+
+        // Optimize the scenario where we're wrapping another SubKeyMap
+        if (base instanceof MockSubKeyMap) {
+            _base = ((MockSubKeyMap) base)._base;
+            _prefix = ((MockSubKeyMap) base)._prefix + prefix;
+        }
+        else {
+            _base = base;
+            _prefix = prefix;
+        }
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return entrySet().isEmpty();
+    }
+
+    @Override
+    public V get(Object key)
+    {
+        key = _getBaseKey(key);
+        return (V) _base.get(key);
+    }
+
+    @Override
+    public V put(String key, V value)
+    {
+        key = _getBaseKey(key);
+        return (V) _base.put(key, value);
+    }
+
+    @Override
+    public V remove(Object key)
+    {
+        key = _getBaseKey(key);
+        return (V) _base.remove(key);
+    }
+
+    @Override
+    public boolean containsKey(Object key)
+    {
+        if (!(key instanceof String)) {
+            return false;
+        }
+
+        return _base.containsKey(_getBaseKey(key));
+    }
+
+    @Override
+    public Set<Map.Entry<String, V>> entrySet()
+    {
+        if (_entrySet == null) {
+            _entrySet = new Entries<V>();
+        }
+        return _entrySet;
+    }
+
+    private String _getBaseKey(Object key)
+    {
+        if (key == null) {
+            throw new NullPointerException();
+        }
+        // Yes, I want a ClassCastException if it's not a String
+        return _prefix + ((String) key);
+    }
+
+    private List<String> _gatherKeys()
+    {
+        List<String> list = new ArrayList<String>();
+        for (String key : _base.keySet()) {
+            if (key != null && key.startsWith(_prefix)) {
+                list.add(key);
+            }
+        }
+
+        return list;
+    }
+
+    //
+    // Set implementation for SubkeyMap.entrySet()
+    //
+
+    private class Entries<V> extends AbstractSet<Map.Entry<String, V>>
+    {
+        public Entries()
+        {
+        }
+
+        @Override
+        public Iterator<Map.Entry<String, V>> iterator()
+        {
+            // Sadly, if you just try to use a filtering approach
+            // on the iterator, you'll get concurrent modification
+            // exceptions.  Consequently, gather the keys in a list
+            // and iterator over that.
+            List<String> keyList = _gatherKeys();
+            return new EntryIterator<V>(keyList.iterator());
+        }
+
+        @Override
+        public int size()
+        {
+            int size = 0;
+            for (String key : _base.keySet()) {
+                if (key != null && key.startsWith(_prefix)) {
+                    size++;
+                }
+            }
+
+            return size;
+        }
+
+        @Override
+        public boolean isEmpty()
+        {
+            Iterator<String> keys = _base.keySet().iterator();
+            while (keys.hasNext()) {
+                String key = keys.next();
+                // Short-circuit:  the default implementation would always
+                // need to iterate to find the total size.
+                if (key != null && key.startsWith(_prefix)) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        @Override
+        public void clear()
+        {
+            Iterator<String> keys = _base.keySet().iterator();
+            while (keys.hasNext()) {
+                String key = keys.next();
+                if (key != null && key.startsWith(_prefix)) {
+                    keys.remove();
+                }
+            }
+        }
+    }
+
+    private class EntryIterator<V> implements Iterator<Map.Entry<String, V>>
+    {
+        public EntryIterator(Iterator<String> iterator)
+        {
+            _iterator = iterator;
+        }
+
+        public boolean hasNext()
+        {
+            return _iterator.hasNext();
+        }
+
+        public Map.Entry<String, V> next()
+        {
+            String baseKey = _iterator.next();
+            _currentKey = baseKey;
+            return new Entry<V>(baseKey);
+        }
+
+        public void remove()
+        {
+            if (_currentKey == null) {
+                throw new IllegalStateException();
+            }
+
+            _base.remove(_currentKey);
+
+            _currentKey = null;
+        }
+
+        private Iterator<String> _iterator;
+        private String _currentKey;
+    }
+
+    private class Entry<V> implements Map.Entry<String, V>
+    {
+        public Entry(String baseKey)
+        {
+            _baseKey = baseKey;
+        }
+
+        public String getKey()
+        {
+            if (_key == null) {
+                _key = _baseKey.substring(_prefix.length());
+            }
+            return _key;
+        }
+
+        public V getValue()
+        {
+            return (V) _base.get(_baseKey);
+        }
+
+        public V setValue(V value)
+        {
+            return (V) _base.put(_baseKey, value);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public boolean equals(Object o)
+        {
+            if (!(o instanceof Map.Entry)) {
+                return false;
+            }
+            Map.Entry<String, V> e = (Map.Entry<String, V>) o;
+            return _equals(getKey(), e.getKey())
+                && _equals(getValue(), e.getValue());
+        }
+
+        @Override
+        public int hashCode()
+        {
+            Object key = getKey();
+            Object value = getValue();
+            return ((key == null) ? 0 : key.hashCode())
+                ^ ((value == null) ? 0 : value.hashCode());
+        }
+
+        private String _baseKey;
+        private String _key;
+    }
+
+    static private boolean _equals(Object a, Object b)
+    {
+        if (a == null) {
+            return b == null;
+        }
+        return a.equals(b);
+    }
+
+    private final Map<String, Object> _base;
+    private final String _prefix;
+    private Set<Map.Entry<String, V>> _entrySet;
+
+}
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/resource/MockResource.java b/test20/src/main/java/org/apache/myfaces/test/mock/resource/MockResource.java
new file mode 100644
index 0000000..2892fa3
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/resource/MockResource.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock.resource;
+
+import org.apache.myfaces.test.mock.MockServletContext;
+
+import javax.faces.application.Resource;
+import javax.faces.context.FacesContext;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Map;
+
+/**
+ * <p>Mock implementation of <code>Resource</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 2.0
+ */
+public class MockResource extends Resource
+{
+
+    private String _prefix;
+    private String _libraryName;
+    private String _libraryVersion;
+    private String _resourceName;
+    private String _resourceVersion;
+    private File _documentRoot;
+
+    /**
+     * Creates new resource object
+     *
+     * @param prefix          locale prefix if any
+     * @param libraryName     resource library name
+     * @param libraryVersion  resource library version if any
+     * @param resourceName    resource file name
+     * @param resourceVersion resource version if any
+     * @param documentRoot    parent folder of resource directories. Must not be <code>null</code>
+     */
+    public MockResource(String prefix, String libraryName, String libraryVersion, String resourceName, String resourceVersion, File documentRoot)
+    {
+        _prefix = prefix;
+        _libraryName = libraryName;
+        _libraryVersion = libraryVersion;
+        _resourceName = resourceName;
+        _resourceVersion = resourceVersion;
+        _documentRoot = documentRoot;
+
+        if (_documentRoot == null) {
+            throw new IllegalArgumentException("documentRoot must not be null");
+        }
+    }
+
+    @Override
+    public String getResourceName()
+    {
+        return _resourceName;
+    }
+
+    @Override
+    public String getLibraryName()
+    {
+        return _libraryName;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        MockServletContext servletContext = (MockServletContext)
+            FacesContext.getCurrentInstance().getExternalContext().getContext();
+        servletContext.setDocumentRoot(_documentRoot);
+        return servletContext.getResourceAsStream(buildResourcePath());
+    }
+
+    @Override
+    public String getRequestPath()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, String> getResponseHeaders()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public URL getURL()
+    {
+        MockServletContext servletContext = (MockServletContext)
+            FacesContext.getCurrentInstance().getExternalContext().getContext();
+        servletContext.setDocumentRoot(_documentRoot);
+
+        try {
+            return servletContext.getResource(buildResourcePath());
+        } catch (MalformedURLException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public boolean userAgentNeedsUpdate(FacesContext context)
+    {
+        return true;
+    }
+
+    private String buildResourcePath()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append('/');
+        boolean firstSlashAdded = false;
+        if (_prefix != null && _prefix.length() > 0) {
+            builder.append(_prefix);
+            firstSlashAdded = true;
+        }
+        if (_libraryName != null) {
+            if (firstSlashAdded) {
+                builder.append('/');
+            }
+            builder.append(_libraryName);
+            firstSlashAdded = true;
+        }
+        if (_libraryVersion != null) {
+            if (firstSlashAdded) {
+                builder.append('/');
+            }
+            builder.append(_libraryVersion);
+            firstSlashAdded = true;
+        }
+        if (_resourceName != null) {
+            if (firstSlashAdded) {
+                builder.append('/');
+            }
+            builder.append(_resourceName);
+            firstSlashAdded = true;
+        }
+        if (_resourceVersion != null) {
+            if (firstSlashAdded) {
+                builder.append('/');
+            }
+            builder.append(_resourceVersion);
+            builder.append(
+                _resourceName.substring(_resourceName.lastIndexOf('.')));
+        }
+
+        return builder.toString();
+    }
+
+}
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceHandler.java b/test20/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceHandler.java
new file mode 100644
index 0000000..75addb5
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/resource/MockResourceHandler.java
@@ -0,0 +1,351 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock.resource;
+
+import org.apache.myfaces.test.mock.MockServletContext;
+
+import javax.faces.FacesException;
+import javax.faces.application.Resource;
+import javax.faces.application.ResourceHandler;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.*;
+import java.util.regex.Pattern;
+
+/**
+ * <p>Mock implementation of <code>ResourceHandler</code>.</p>
+ * <p/>
+ * $Id$
+ * 
+ * @since 2.0
+ */
+public class MockResourceHandler extends ResourceHandler
+{
+
+    private static final String IS_RESOURCE_REQUEST = "org.apache.myfaces.IS_RESOURCE_REQUEST";
+
+    /**
+     * It checks version like this: /1/, /1_0/, /1_0_0/, /100_100/
+     * <p/>
+     * Used on getLibraryVersion to filter resource directories
+     */
+    protected static Pattern VERSION_CHECKER = Pattern.compile("/\\p{Digit}+(_\\p{Digit}*)*/");
+
+    /**
+     * It checks version like this: /1.js, /1_0.js, /1_0_0.js, /100_100.js
+     * <p/>
+     * Used on getResourceVersion to filter resources
+     */
+    protected static Pattern RESOURCE_VERSION_CHECKER = Pattern.compile("/\\p{Digit}+(_\\p{Digit}*)*\\..*");
+
+    private File _documentRoot;
+
+    /**
+     * @param documentRoot parent folder of resource directories. Must not be <code>null</code>
+     */
+    public MockResourceHandler(File documentRoot)
+    {
+        if (documentRoot == null) {
+            throw new NullPointerException("documentRoot must not be null");
+        }
+
+        _documentRoot = documentRoot;
+
+        ((MockServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext())
+            .setDocumentRoot(_documentRoot);
+    }
+
+    @Override
+    public Resource createResource(String resourceName)
+    {
+        return createResource(resourceName, null);
+    }
+
+    @Override
+    public Resource createResource(String resourceName, String libraryName)
+    {
+        return createResource(resourceName, libraryName, null);
+    }
+
+    @Override
+    public Resource createResource(String resourceName, String libraryName, String contentType)
+    {
+        String prefix = getLocalePrefixForLocateResource();
+        String libraryVersion = getLibraryVersion(prefix + "/" + libraryName);
+
+        String pathToResource;
+        if (null != libraryVersion) {
+            pathToResource = prefix + '/'
+                + libraryName + '/' + libraryVersion + '/'
+                + resourceName;
+        }
+        else {
+            pathToResource = prefix + '/'
+                + libraryName + '/' + resourceName;
+        }
+
+        return new MockResource(prefix, libraryName, libraryVersion, resourceName, getResourceVersion(pathToResource), _documentRoot);
+    }
+
+    @Override
+    public String getRendererTypeForResourceName(String resourceName)
+    {
+        if (resourceName.endsWith(".js")) {
+            return "javax.faces.resource.Script";
+        }
+        else if (resourceName.endsWith(".css")) {
+            return "javax.faces.resource.Stylesheet";
+        }
+        return null;
+    }
+
+    @Override
+    public void handleResourceRequest(FacesContext context) throws IOException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isResourceRequest(FacesContext facesContext)
+    {
+        // Since this method could be called many times we save it
+        //on request map so the first time is calculated it remains
+        //alive until the end of the request
+        Boolean value = (Boolean) facesContext.getExternalContext()
+            .getRequestMap().get(IS_RESOURCE_REQUEST);
+
+        if (value != null && value.booleanValue()) {
+            //return the saved value
+            return value.booleanValue();
+        }
+        else {
+            // assuming that we don't have servlet mapping
+            String resourceBasePath = facesContext.getExternalContext().getRequestPathInfo();
+
+            if (resourceBasePath != null
+                && resourceBasePath
+                .startsWith(ResourceHandler.RESOURCE_IDENTIFIER)) {
+                facesContext.getExternalContext().getRequestMap().put(
+                    IS_RESOURCE_REQUEST, Boolean.TRUE);
+                return true;
+            }
+            else {
+                facesContext.getExternalContext().getRequestMap().put(
+                    IS_RESOURCE_REQUEST, Boolean.FALSE);
+                return false;
+            }
+        }
+    }
+
+    @Override
+    public boolean libraryExists(String libraryName)
+    {
+        String localePrefix = getLocalePrefixForLocateResource();
+
+        String pathToLib;
+
+        if (localePrefix != null) {
+            //Check with locale
+            pathToLib = localePrefix + '/' + libraryName;
+        }
+        else {
+            pathToLib = libraryName;
+        }
+
+        try {
+            URL url = FacesContext.getCurrentInstance().getExternalContext().getResource("/" + pathToLib);
+            return (url != null);
+        }
+        catch (MalformedURLException e) {
+            return false;
+        }
+    }
+
+    protected String getLocalePrefixForLocateResource()
+    {
+        String localePrefix = null;
+        FacesContext context = FacesContext.getCurrentInstance();
+
+        String bundleName = context.getApplication().getMessageBundle();
+
+        if (null != bundleName) {
+            Locale locale = context.getApplication().getViewHandler()
+                .calculateLocale(context);
+
+            ResourceBundle bundle = ResourceBundle
+                .getBundle(bundleName, locale, getContextClassLoader());
+
+            if (bundle != null) {
+                try {
+                    localePrefix = bundle.getString(ResourceHandler.LOCALE_PREFIX);
+                }
+                catch (MissingResourceException e) {
+                    // Ignore it and return null
+                }
+            }
+        }
+        return localePrefix;
+    }
+
+    /**
+     * Gets the ClassLoader associated with the current thread.  Includes a check for priviledges
+     * against java2 security to ensure no security related exceptions are encountered.
+     *
+     * @return ClassLoader
+     * @since 3.0.6
+     */
+    private static ClassLoader getContextClassLoader()
+    {
+        if (System.getSecurityManager() != null) {
+            try {
+                ClassLoader cl = AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>()
+                {
+                    public ClassLoader run() throws PrivilegedActionException
+                    {
+                        return Thread.currentThread().getContextClassLoader();
+                    }
+                });
+                return cl;
+            }
+            catch (PrivilegedActionException pae) {
+                throw new FacesException(pae);
+            }
+        }
+        else {
+            return Thread.currentThread().getContextClassLoader();
+        }
+    }
+
+    private String getLibraryVersion(String path)
+    {
+        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
+
+        String libraryVersion = null;
+        Set<String> libraryPaths = context.getResourcePaths("/" + path);
+        if (null != libraryPaths && !libraryPaths.isEmpty()) {
+            // Look in the libraryPaths for versioned libraries.
+            // If one or more versioned libraries are found, take
+            // the one with the "highest" version number as the value
+            // of libraryVersion. If no versioned libraries
+            // are found, let libraryVersion remain null.
+
+            for (String libraryPath : libraryPaths) {
+                String version = libraryPath.substring(path.length());
+
+                if (VERSION_CHECKER.matcher(version).matches()) {
+                    version = version.substring(1, version.length() - 1);
+                    if (libraryVersion == null) {
+                        libraryVersion = version;
+                    }
+                    else if (compareVersion(libraryVersion, version) < 0) {
+                        libraryVersion = version;
+                    }
+                }
+            }
+        }
+        return libraryVersion;
+    }
+
+    private int compareVersion(String s1, String s2)
+    {
+        int n1 = 0;
+        int n2 = 0;
+        String o1 = s1;
+        String o2 = s2;
+
+        boolean p1 = true;
+        boolean p2 = true;
+
+        while (n1 == n2 && (p1 || p2)) {
+            int i1 = o1.indexOf('_');
+            int i2 = o2.indexOf('_');
+            if (i1 < 0) {
+                if (o1.length() > 0) {
+                    p1 = false;
+                    n1 = Integer.valueOf(o1);
+                    o1 = "";
+                }
+                else {
+                    p1 = false;
+                    n1 = 0;
+                }
+            }
+            else {
+                n1 = Integer.valueOf(o1.substring(0, i1));
+                o1 = o1.substring(i1 + 1);
+            }
+            if (i2 < 0) {
+                if (o2.length() > 0) {
+                    p2 = false;
+                    n2 = Integer.valueOf(o2);
+                    o2 = "";
+                }
+                else {
+                    p2 = false;
+                    n2 = 0;
+                }
+            }
+            else {
+                n2 = Integer.valueOf(o2.substring(0, i2));
+                o2 = o2.substring(i2 + 1);
+            }
+        }
+
+        if (n1 == n2) {
+            return s1.length() - s2.length();
+        }
+        return n1 - n2;
+    }
+
+    private String getResourceVersion(String path)
+    {
+        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
+        String resourceVersion = null;
+        Set<String> resourcePaths = context.getResourcePaths("/" + path);
+
+        if (null != resourcePaths && !resourcePaths.isEmpty()) {
+            // resourceVersion = // execute the comment
+            // Look in the resourcePaths for versioned resources.
+            // If one or more versioned resources are found, take
+            // the one with the "highest" version number as the value
+            // of resourceVersion. If no versioned libraries
+            // are found, let resourceVersion remain null.
+            for (String resourcePath : resourcePaths) {
+                String version = resourcePath.substring(path.length());
+
+                if (RESOURCE_VERSION_CHECKER.matcher(version).matches()) {
+                    version = version.substring(1, version.lastIndexOf('.'));
+                    if (resourceVersion == null) {
+                        resourceVersion = version;
+                    }
+                    else if (compareVersion(resourceVersion, version) < 0) {
+                        resourceVersion = version;
+                    }
+                }
+            }
+        }
+        return resourceVersion;
+    }
+}
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitCallback.java b/test20/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitCallback.java
new file mode 100644
index 0000000..ac64f40
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitCallback.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock.visit;
+
+import javax.faces.component.UIComponent;
+import javax.faces.component.visit.VisitCallback;
+import javax.faces.component.visit.VisitContext;
+import javax.faces.component.visit.VisitResult;
+
+/**
+ * <p>Mock implementation of <code>VisitCallback</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 2.0
+ */
+public class MockVisitCallback implements VisitCallback
+{
+
+    public VisitResult visit(VisitContext context, UIComponent target)
+    {
+        return VisitResult.ACCEPT;
+    }
+}
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitContext.java b/test20/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitContext.java
new file mode 100644
index 0000000..c2d3dbd
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitContext.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock.visit;
+
+import javax.faces.component.NamingContainer;
+import javax.faces.component.UIComponent;
+import javax.faces.component.visit.VisitCallback;
+import javax.faces.component.visit.VisitContext;
+import javax.faces.component.visit.VisitHint;
+import javax.faces.component.visit.VisitResult;
+import javax.faces.context.FacesContext;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * <p>Mock implementation of <code>VisitContext</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 2.0
+ */
+public class MockVisitContext extends VisitContext
+{
+
+    private final FacesContext _facesContext;
+    private final Set<VisitHint> _hints;
+
+    public MockVisitContext(FacesContext facesContext)
+    {
+        this(facesContext, null);
+    }
+
+    public MockVisitContext(FacesContext facesContext, Set<VisitHint> hints)
+    {
+        if (facesContext == null) {
+            throw new NullPointerException();
+        }
+
+        _facesContext = facesContext;
+
+        // Copy and store hints - ensure unmodifiable and non-empty
+        EnumSet<VisitHint> hintsEnumSet = ((hints == null) || (hints.isEmpty()))
+            ? EnumSet.noneOf(VisitHint.class)
+            : EnumSet.copyOf(hints);
+
+        _hints = Collections.unmodifiableSet(hintsEnumSet);
+    }
+
+    @Override
+    public FacesContext getFacesContext()
+    {
+        return _facesContext;
+    }
+
+    @Override
+    public Set<VisitHint> getHints()
+    {
+        return _hints;
+    }
+
+    @Override
+    public Collection<String> getIdsToVisit()
+    {
+        return ALL_IDS;
+    }
+
+    @Override
+    public Collection<String> getSubtreeIdsToVisit(UIComponent component)
+    {
+        // Make sure component is a NamingContainer
+        if (!(component instanceof NamingContainer)) {
+            throw new IllegalArgumentException("Component is not a NamingContainer: " + component);
+        }
+
+        return ALL_IDS;
+    }
+
+    @Override
+    public VisitResult invokeVisitCallback(UIComponent component, VisitCallback callback)
+    {
+        return callback.visit(this, component);
+    }
+}
diff --git a/test20/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitContextFactory.java b/test20/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitContextFactory.java
new file mode 100644
index 0000000..155e7c2
--- /dev/null
+++ b/test20/src/main/java/org/apache/myfaces/test/mock/visit/MockVisitContextFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock.visit;
+
+import javax.faces.component.visit.VisitContext;
+import javax.faces.component.visit.VisitContextFactory;
+import javax.faces.component.visit.VisitHint;
+import javax.faces.context.FacesContext;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * <p>Mock implementation of <code>VisitContextFactory</code>.</p>
+ * <p/>
+ * $Id$
+ *
+ * @since 2.0
+ */
+public class MockVisitContextFactory extends VisitContextFactory
+{
+
+    @Override
+    public VisitContext getVisitContext(FacesContext context, Collection<String> ids, Set<VisitHint> hints)
+    {
+        return new MockVisitContext(context, hints);
+    }
+}
diff --git a/test20/src/test/java/org/apache/myfaces/test/config/ConfigParser20TestCase.java b/test20/src/test/java/org/apache/myfaces/test/config/ConfigParser20TestCase.java
new file mode 100644
index 0000000..4c6f928
--- /dev/null
+++ b/test20/src/test/java/org/apache/myfaces/test/config/ConfigParser20TestCase.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.myfaces.test.config;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.myfaces.test.base.AbstractJsfTestCase;
+import org.apache.myfaces.test.mock.MockApplication20;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Test case for <code>ConfigParser20</code>
+ */
+public class ConfigParser20TestCase extends AbstractJsfTestCase
+{
+
+
+    // ------------------------------------------------------------ Constructors
+
+
+    // Construct a new instance of this test case.
+
+    public ConfigParser20TestCase(String name)
+    {
+        super(name);
+    }
+
+
+    // ---------------------------------------------------- Overall Test Methods
+
+
+    // Set up instance variables required by this test case.
+
+    protected void setUp() throws Exception
+    {
+
+        super.setUp();
+        parser = new ConfigParser20();
+        application20 = (MockApplication20) application;
+
+    }
+
+
+    // Return the tests included in this test case.
+
+    public static Test suite()
+    {
+
+        return (new TestSuite(ConfigParser20TestCase.class));
+
+    }
+
+
+    // Tear down instance variables required by this test case.
+
+    protected void tearDown() throws Exception
+    {
+
+        parser = null;
+        super.tearDown();
+
+    }
+
+
+    // ------------------------------------------------------ Instance Variables
+
+
+    // ConfigParser instance under test
+    ConfigParser20 parser = null;
+
+    MockApplication20 application20 = null;
+
+    // ------------------------------------------------- Individual Test Methods
+
+    @SuppressWarnings("unchecked")
+    public void testSimple() throws Exception
+    {
+
+        URL url = this.getClass().getResource("/org/apache/myfaces/test/config/myfaces20-faces-config-1.xml");
+        assertNotNull(url);
+        parser.parse(url);
+        Iterator items;
+        List list = new ArrayList();
+
+        items = application20.getComponentTypes();
+        list.clear();
+        while (items.hasNext()) {
+            list.add(items.next());
+        }
+        assertTrue(list.contains("component-type-1"));
+        assertTrue(list.contains("component-type-2"));
+
+        items = application20.getBehaviorIds();
+        list.clear();
+        while (items.hasNext()) {
+            list.add(items.next());
+        }
+        assertTrue(list.contains("behavior-1"));
+        assertTrue(list.contains("behavior-2"));
+    }
+}
diff --git a/test20/src/test/java/org/apache/myfaces/test/config/MyRenderer.java b/test20/src/test/java/org/apache/myfaces/test/config/MyRenderer.java
new file mode 100644
index 0000000..20d6745
--- /dev/null
+++ b/test20/src/test/java/org/apache/myfaces/test/config/MyRenderer.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.config;
+
+import javax.faces.render.Renderer;
+
+/**
+ * <p>Concrete renderer implementation for testing.</p>
+ */
+public class MyRenderer extends Renderer {
+    
+}
diff --git a/test20/src/test/java/org/apache/myfaces/test/mock/MockApplication20TestCase.java b/test20/src/test/java/org/apache/myfaces/test/mock/MockApplication20TestCase.java
new file mode 100644
index 0000000..bd56e68
--- /dev/null
+++ b/test20/src/test/java/org/apache/myfaces/test/mock/MockApplication20TestCase.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.myfaces.test.base.AbstractJsfTestCase;
+
+import javax.faces.application.ProjectStage;
+import javax.faces.component.behavior.Behavior;
+
+/**
+ * Test case for <code>MockApplication20</code>
+ */
+public class MockApplication20TestCase extends AbstractJsfTestCase
+{
+
+    private MockApplication20 _application;
+
+    public MockApplication20TestCase(String name)
+    {
+        super(name);
+    }
+
+    // Set up instance variables required by this test case.
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        _application = (MockApplication20) application;
+    }
+
+    // Return the tests included in this test case.
+
+    public static Test suite()
+    {
+        return (new TestSuite(MockApplication20TestCase.class));
+    }
+
+    // Tear down instance variables required by this test case.
+
+    protected void tearDown() throws Exception
+    {
+        super.tearDown();
+    }
+
+    public void testGetProjectStage()
+    {
+        assertEquals(ProjectStage.Production, _application.getProjectStage());
+    }
+
+    public void testBehavior()
+    {
+        String behaviorId = "BehaviorBaseId";
+
+        _application.addBehavior(behaviorId,
+            "javax.faces.component.behavior.BehaviorBase");
+
+        assertTrue("Behavior not added", _application.getBehaviorIds().hasNext());
+        assertEquals(behaviorId, _application.getBehaviorIds().next());
+
+        Behavior createdBehavior = _application.createBehavior(behaviorId);
+        assertNotNull("Behavior not created", createdBehavior);
+    }
+
+    public void testDefaultValidator()
+    {
+        String validatorId = "testValidator";
+        String validatorClass = "javax.faces.validator.LengthValidator";
+
+        _application.addValidator(validatorId, validatorClass);
+        assertTrue(_application.getValidatorIds().hasNext());
+
+        _application.addDefaultValidatorId(validatorId);
+        assertTrue(_application.getDefaultValidatorInfo().containsKey(validatorId));
+    }
+}
diff --git a/test20/src/test/java/org/apache/myfaces/test/mock/MockExternalContext20TestCase.java b/test20/src/test/java/org/apache/myfaces/test/mock/MockExternalContext20TestCase.java
new file mode 100644
index 0000000..8b8483d
--- /dev/null
+++ b/test20/src/test/java/org/apache/myfaces/test/mock/MockExternalContext20TestCase.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.myfaces.test.base.AbstractJsfTestCase;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Test case for <code>MockExternalContext20</code>
+ */
+public class MockExternalContext20TestCase extends AbstractJsfTestCase
+{
+
+    private MockExternalContext20 _context;
+
+    public MockExternalContext20TestCase(String name)
+    {
+        super(name);
+    }
+
+    // Set up instance variables required by this test case.
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        _context = (MockExternalContext20) externalContext;
+    }
+
+    // Return the tests included in this test case.
+
+    public static Test suite()
+    {
+        return (new TestSuite(MockExternalContext20TestCase.class));
+    }
+
+    // Tear down instance variables required by this test case.
+
+    protected void tearDown() throws Exception
+    {
+        super.tearDown();
+    }
+
+    public void testEncodeBookmarkableURL()
+    {
+        HashMap<String, List<String>> parameters = new HashMap<String, List<String>>();
+        List<String> valueList = new ArrayList<String>();
+        valueList.add("value1");
+        parameters.put("param1", valueList);
+
+        assertEquals("http://localhost:8080?param1=value1",
+            _context.encodeBookmarkableURL("http://localhost:8080", parameters));
+    }
+
+    public void testEncodeRedirectURL()
+    {
+        HashMap<String, List<String>> parameters = new HashMap<String, List<String>>();
+        List<String> valueList = new ArrayList<String>();
+        valueList.add("value1");
+        parameters.put("param1", valueList);
+
+        assertEquals("http://localhost:8080?param1=value1",
+            _context.encodeRedirectURL("http://localhost:8080", parameters));
+    }
+
+    public void testGetContextName()
+    {
+        assertEquals("MockServletContext", _context.getContextName());
+    }
+
+    public void testResponseSendError() throws Exception
+    {
+        _context.responseSendError(404, "not found");
+        assertEquals(404,
+            ((MockHttpServletResponse) _context.getResponse()).getStatus());
+    }
+
+    public void testResponseHeader() throws Exception
+    {
+        _context.setResponseHeader("header1", "value1");
+        assertEquals("value1",
+            ((MockHttpServletResponse) _context.getResponse()).getHeader("header1"));
+    }
+
+    public void testGetRequestScheme()
+    {
+        assertEquals("http", _context.getRequestScheme());
+    }
+
+    public void testGetRequestServerName()
+    {
+        assertEquals("localhost", _context.getRequestServerName());
+    }
+
+    public void testGetRequestServerPort()
+    {
+        assertEquals(8080, _context.getRequestServerPort());
+    }
+
+    public void testGetResponseOutputStream() throws Exception
+    {
+        assertNotNull(_context.getResponseOutputStream());
+    }
+
+    public void testGetResponseOutputWriter() throws Exception
+    {
+        assertNotNull(_context.getResponseOutputWriter());
+    }
+
+    public void testGetFlash() throws Exception
+    {
+        assertNotNull(_context.getFlash());
+    }
+}
diff --git a/test20/src/test/java/org/apache/myfaces/test/mock/MockFacesContext20TestCase.java b/test20/src/test/java/org/apache/myfaces/test/mock/MockFacesContext20TestCase.java
new file mode 100644
index 0000000..ede945c
--- /dev/null
+++ b/test20/src/test/java/org/apache/myfaces/test/mock/MockFacesContext20TestCase.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.myfaces.test.base.AbstractJsfTestCase;
+
+import javax.faces.application.FacesMessage;
+import javax.faces.context.ExceptionHandler;
+import javax.faces.context.ExceptionHandlerFactory;
+import java.util.List;
+
+/**
+ * Test case for <code>MockFacesContext20</code>
+ */
+public class MockFacesContext20TestCase extends AbstractJsfTestCase
+{
+
+    private MockFacesContext20 _context;
+
+    public MockFacesContext20TestCase(String name)
+    {
+        super(name);
+    }
+
+    // Set up instance variables required by this test case.
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        _context = (MockFacesContext20) facesContext;
+    }
+
+    // Return the tests included in this test case.
+
+    public static Test suite()
+    {
+        return (new TestSuite(MockFacesContext20TestCase.class));
+    }
+
+    // Tear down instance variables required by this test case.
+
+    protected void tearDown() throws Exception
+    {
+        super.tearDown();
+    }
+
+    public void testSetGetExceptionHandler()
+    {
+        ExceptionHandlerFactory handlerFactory = new MockExceptionHandlerFactory();
+        ExceptionHandler handler = handlerFactory.getExceptionHandler();
+        _context.setExceptionHandler(handler);
+        assertEquals(handler, _context.getExceptionHandler());
+    }
+
+    public void testGetMessageList()
+    {
+        FacesMessage message1 = new FacesMessage("test message1");
+        FacesMessage message2 = new FacesMessage("test message2");
+        _context.addMessage("clientid1", message1);
+        _context.addMessage("clientid2", message2);
+
+        List<FacesMessage> messageList = _context.getMessageList();
+        assertEquals(2, messageList.size());
+        assertTrue(messageList.contains(message1));
+        assertTrue(messageList.contains(message2));
+
+        List<FacesMessage> messageListClientId = _context.getMessageList("clientid1");
+        assertEquals(1, messageListClientId.size());
+        assertTrue(messageListClientId.contains(message1));
+    }
+
+    public void testValidationFailed()
+    {
+        assertFalse(_context.isValidationFailed());
+        _context.validationFailed();
+        assertTrue(_context.isValidationFailed());
+    }
+}
diff --git a/test20/src/test/java/org/apache/myfaces/test/mock/MockFlashTestCase.java b/test20/src/test/java/org/apache/myfaces/test/mock/MockFlashTestCase.java
new file mode 100644
index 0000000..c0373c3
--- /dev/null
+++ b/test20/src/test/java/org/apache/myfaces/test/mock/MockFlashTestCase.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.myfaces.test.base.AbstractJsfTestCase;
+
+import javax.faces.context.Flash;
+
+/**
+ * Test case for <code>MockFlash</code>
+ */
+public class MockFlashTestCase extends AbstractJsfTestCase
+{
+
+    public MockFlashTestCase(String name)
+    {
+        super(name);
+    }
+
+    // Set up instance variables required by this test case.
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+    }
+
+    // Return the tests included in this test case.
+
+    public static Test suite()
+    {
+        return (new TestSuite(MockFlashTestCase.class));
+    }
+
+    // Tear down instance variables required by this test case.
+
+    protected void tearDown() throws Exception
+    {
+        super.tearDown();
+    }
+
+    public void testGetInstance()
+    {
+        Flash flash = MockFlash.getCurrentInstance(externalContext);
+        assertNotNull(flash);
+        assertTrue(flash instanceof MockFlash);
+
+        Flash flash2 = MockFlash.getCurrentInstance(externalContext);
+        assertNotNull(flash2);
+        assertTrue(flash2 instanceof MockFlash);
+        assertEquals(flash, flash2);
+    }
+
+    public void testIsRedirect()
+    {
+        Flash flash = MockFlash.getCurrentInstance(externalContext);
+        assertFalse(flash.isRedirect());
+        flash.setRedirect(true);
+        assertTrue(flash.isRedirect());
+    }
+
+}
diff --git a/test20/src/test/java/org/apache/myfaces/test/mock/MockPartialViewContextTestCase.java b/test20/src/test/java/org/apache/myfaces/test/mock/MockPartialViewContextTestCase.java
new file mode 100644
index 0000000..e877d91
--- /dev/null
+++ b/test20/src/test/java/org/apache/myfaces/test/mock/MockPartialViewContextTestCase.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.myfaces.test.base.AbstractJsfTestCase;
+
+import javax.faces.context.FacesContext;
+import javax.faces.context.PartialViewContext;
+import javax.faces.context.PartialViewContextFactory;
+
+/**
+ * Test case for <code>MockPartialViewContext</code>
+ */
+public class MockPartialViewContextTestCase extends AbstractJsfTestCase
+{
+
+    public MockPartialViewContextTestCase(String name)
+    {
+        super(name);
+    }
+
+    // Set up instance variables required by this test case.
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+    }
+
+    // Return the tests included in this test case.
+
+    public static Test suite()
+    {
+        return (new TestSuite(MockPartialViewContextTestCase.class));
+    }
+
+    // Tear down instance variables required by this test case.
+
+    protected void tearDown() throws Exception
+    {
+        super.tearDown();
+    }
+
+    public void testIsAjaxRequest()
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        PartialViewContextFactory factory = new MockPartialViewContextFactory();
+
+        PartialViewContext pvContext = factory.getPartialViewContext(facesContext);
+        assertFalse(pvContext.isAjaxRequest());
+
+        facesContext.getExternalContext().getRequestHeaderMap().put("Faces-Request", "partial/ajax");
+
+        pvContext = factory.getPartialViewContext(facesContext);
+        assertTrue(pvContext.isAjaxRequest());
+    }
+}
diff --git a/test20/src/test/java/org/apache/myfaces/test/mock/MockResourceTestCase.java b/test20/src/test/java/org/apache/myfaces/test/mock/MockResourceTestCase.java
new file mode 100644
index 0000000..e5d9fe1
--- /dev/null
+++ b/test20/src/test/java/org/apache/myfaces/test/mock/MockResourceTestCase.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.myfaces.test.base.AbstractJsfTestCase;
+import org.apache.myfaces.test.mock.resource.MockResource;
+import org.apache.myfaces.test.mock.resource.MockResourceHandler;
+
+import javax.faces.application.Resource;
+import javax.faces.application.ResourceHandler;
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * Test case for resource handling
+ */
+public class MockResourceTestCase extends AbstractJsfTestCase {
+
+    private File _documentRoot;
+
+    public MockResourceTestCase(String name) {
+        super(name);
+    }
+
+    // Set up instance variables required by this test case.
+    protected void setUp() throws Exception {
+        super.setUp();
+        _documentRoot = new File("src/test/resources/org/apache/myfaces/test/mock/resources");
+    }
+
+    // Return the tests included in this test case.
+    public static Test suite() {
+        return (new TestSuite(MockResourceTestCase.class));
+    }
+
+    // Tear down instance variables required by this test case.
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testGetResource() throws Exception {
+
+        Resource resource = new MockResource(null, "testlib", null, "testfile.js", null, _documentRoot);
+
+        URL resourceUrl = resource.getURL();
+        assertNotNull("Could not find resource", resourceUrl);
+        assertTrue(resourceUrl.toString().endsWith("org/apache/myfaces/test/mock/resources/testlib/testfile.js"));
+    }
+
+    public void testGetNotExistingResource() throws Exception {
+
+        Resource resource = new MockResource(null, "testlib", null, "notexisting.js", null, _documentRoot);
+
+        assertNull(resource.getURL());
+    }
+
+    public void testGetAsStream() throws Exception {
+        Resource resource = new MockResource(null, "testlib", null, "testfile.js", null, _documentRoot);
+        InputStream stream = resource.getInputStream();
+        assertNotNull(stream);
+        assertTrue(stream.read() != -1);
+    }
+
+    public void testCreateResource() throws Exception {
+        ResourceHandler handler = new MockResourceHandler(_documentRoot);
+        Resource resource = handler.createResource("testfile.js", "testlib");
+        assertNotNull("resource could not be created", resource);
+        assertTrue(resource.getURL().toString().endsWith("org/apache/myfaces/test/mock/resources/testlib/testfile.js"));
+    }
+
+    public void testResourceHandler() throws Exception {
+       ResourceHandler handler = new MockResourceHandler(_documentRoot);
+
+        assertTrue(handler.libraryExists("testlib"));
+        assertFalse(handler.libraryExists("notexistinglib"));
+
+        assertEquals("javax.faces.resource.Script", handler.getRendererTypeForResourceName("testfile.js"));
+    }
+
+}
diff --git a/test20/src/test/java/org/apache/myfaces/test/mock/MockVisitTestCase.java b/test20/src/test/java/org/apache/myfaces/test/mock/MockVisitTestCase.java
new file mode 100644
index 0000000..d7555cc
--- /dev/null
+++ b/test20/src/test/java/org/apache/myfaces/test/mock/MockVisitTestCase.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.myfaces.test.mock;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.myfaces.test.base.AbstractJsfTestCase;
+import org.apache.myfaces.test.mock.visit.MockVisitCallback;
+import org.apache.myfaces.test.mock.visit.MockVisitContextFactory;
+
+import javax.faces.component.UIComponent;
+import javax.faces.component.visit.VisitCallback;
+import javax.faces.component.visit.VisitContext;
+import javax.faces.component.visit.VisitContextFactory;
+import javax.faces.component.visit.VisitResult;
+import javax.faces.context.FacesContext;
+
+/**
+ * Test case for <code>MockVisitContext</code>
+ */
+public class MockVisitTestCase extends AbstractJsfTestCase
+{
+
+    public MockVisitTestCase(String name)
+    {
+        super(name);
+    }
+
+    // Set up instance variables required by this test case.
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+    }
+
+    // Return the tests included in this test case.
+
+    public static Test suite()
+    {
+        return (new TestSuite(MockVisitTestCase.class));
+    }
+
+    // Tear down instance variables required by this test case.
+
+    protected void tearDown() throws Exception
+    {
+        super.tearDown();
+    }
+
+    public void testVisitTree()
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        VisitContextFactory factory = new MockVisitContextFactory();
+        VisitContext visitContext = factory.getVisitContext(facesContext, null, null);
+
+        VisitCallback callback = new MockVisitCallback();
+        UIComponent component = facesContext.getViewRoot();
+        assertEquals(VisitResult.ACCEPT, visitContext.invokeVisitCallback(component, callback));
+    }
+
+}
diff --git a/test20/src/test/resources/org/apache/myfaces/test/config/myfaces20-faces-config-1.xml b/test20/src/test/resources/org/apache/myfaces/test/config/myfaces20-faces-config-1.xml
new file mode 100644
index 0000000..f3df8a4
--- /dev/null
+++ b/test20/src/test/resources/org/apache/myfaces/test/config/myfaces20-faces-config-1.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to you under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<faces-config version="2.0"
+    xmlns="http://java.sun.com/xml/ns/javaee"
+    xmlns:xi="http://www.w3.org/2001/XInclude"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
+
+  <component>
+      <component-type>component-type-1</component-type>
+      <component-class>com.mycompany.MyComponent1</component-class>
+  </component>
+
+  <component>
+      <component-type>component-type-2</component-type>
+      <component-class>com.mycompany.MyComponent2</component-class>
+  </component>
+
+  <converter>
+      <converter-for-class>java.lang.Integer</converter-for-class>
+      <converter-class>org.apache.myfaces.test.config.MyConverter</converter-class>
+  </converter>
+
+  <validator>
+      <validator-id>validator-id-1</validator-id>
+      <validator-class>javax.faces.validator.LengthValidator</validator-class>
+  </validator>
+
+  <validator>
+      <validator-id>validator-id-2</validator-id>
+      <validator-class>javax.faces.validator.LongRangeValidator</validator-class>
+  </validator>
+
+  <behavior>
+      <behavior-id>behavior-1</behavior-id>
+      <behavior-class>org.apache.myfaces.test.config.MyBehavior</behavior-class>
+  </behavior>
+  <behavior>
+      <behavior-id>behavior-2</behavior-id>
+      <behavior-class>org.apache.myfaces.test.config.MyBehavior</behavior-class>
+  </behavior>
+
+  <render-kit>
+
+      <renderer>
+          <component-family>component-family-1</component-family>
+          <renderer-type>renderer-type-1</renderer-type>
+          <renderer-class>org.apache.myfaces.test.config.MyRenderer</renderer-class>
+      </renderer>
+
+      <renderer>
+          <component-family>component-family-2</component-family>
+          <renderer-type>renderer-type-2</renderer-type>
+          <renderer-class>org.apache.myfaces.test.config.MyRenderer</renderer-class>
+      </renderer>
+
+  </render-kit>
+
+
+
+</faces-config>
diff --git a/test20/src/test/resources/org/apache/myfaces/test/mock/resources/testlib/testfile.js b/test20/src/test/resources/org/apache/myfaces/test/mock/resources/testlib/testfile.js
new file mode 100644
index 0000000..91c635b
--- /dev/null
+++ b/test20/src/test/resources/org/apache/myfaces/test/mock/resources/testlib/testfile.js
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2009 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Author: Ingo Hofmann (latest modification by $Author$)
+ * Version: $Revision$ $Date$
+ *
+ */
+function foo() {
+        alert('Hello World');
+}