You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2022/11/27 08:24:53 UTC

[struts] 10/23: WW-5233 Copies Tiles Request related tests

This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch WW-5233-tiles
in repository https://gitbox.apache.org/repos/asf/struts.git

commit c912c4eea03bf0fad34164654626b2661ce0d59e
Author: Lukasz Lenart <lu...@apache.org>
AuthorDate: Sun Oct 2 14:47:34 2022 +0200

    WW-5233 Copies Tiles Request related tests
---
 .../request/locale/URLApplicationResource.java     |  40 ++-
 .../tiles/request/AbstractClientRequestTest.java   | 155 ++++++++
 .../apache/tiles/request/AbstractRequestTest.java  |  54 +++
 .../tiles/request/AbstractViewRequestTest.java     | 119 ++++++
 .../tiles/request/ApplicationAccessTest.java       |  51 +++
 .../tiles/request/DispatchRequestWrapperTest.java  |  90 +++++
 .../request/NotAvailableFeatureExceptionTest.java  |  41 +++
 .../apache/tiles/request/RequestExceptionTest.java |  53 +++
 .../collection/AddableParameterMapTest.java        | 112 ++++++
 .../request/collection/CollectionUtilTest.java     |  59 +++
 .../collection/HeaderValuesCollectionTest.java     | 398 +++++++++++++++++++++
 .../collection/HeaderValuesMapEntrySetTest.java    | 337 +++++++++++++++++
 .../request/collection/HeaderValuesMapTest.java    | 337 +++++++++++++++++
 .../tiles/request/collection/KeySetTest.java       | 283 +++++++++++++++
 .../collection/MapEntryArrayValuesTest.java        |  76 ++++
 .../tiles/request/collection/MapEntryTest.java     | 106 ++++++
 .../ReadOnlyEnumerationMapEntrySetTest.java        | 293 +++++++++++++++
 .../collection/ReadOnlyEnumerationMapTest.java     | 306 ++++++++++++++++
 ...ReadOnlyEnumerationMapValuesCollectionTest.java | 310 ++++++++++++++++
 .../request/collection/RemovableKeySetTest.java    | 129 +++++++
 .../request/collection/ScopeMapEntrySetTest.java   | 234 ++++++++++++
 .../tiles/request/collection/ScopeMapTest.java     | 135 +++++++
 .../tiles/request/locale/LocaleUtilTest.java       |  42 +++
 .../locale/PostfixedApplicationResourceTest.java   | 131 +++++++
 .../request/locale/URLApplicationResourceTest.java | 201 +++++++++++
 .../CannotInstantiateObjectExceptionTest.java      |  41 +++
 .../tiles/request/reflect/ClassUtilTest.java       | 175 +++++++++
 .../request/render/BasicRendererFactoryTest.java   | 100 ++++++
 .../render/ChainedDelegateRendererTest.java        | 178 +++++++++
 .../tiles/request/render/DispatchRendererTest.java |  74 ++++
 .../render/NoSuchRendererExceptionTest.java        |  42 +++
 .../tiles/request/render/StringRendererTest.java   |  79 ++++
 .../tiles/request/locale/resource with space.txt   |  17 +
 .../org/apache/tiles/request/locale/resource.txt   |  17 +
 34 files changed, 4803 insertions(+), 12 deletions(-)

diff --git a/plugins/tiles/src/main/java/org/apache/tiles/request/locale/URLApplicationResource.java b/plugins/tiles/src/main/java/org/apache/tiles/request/locale/URLApplicationResource.java
index 8f867b7fb..e88b25db7 100644
--- a/plugins/tiles/src/main/java/org/apache/tiles/request/locale/URLApplicationResource.java
+++ b/plugins/tiles/src/main/java/org/apache/tiles/request/locale/URLApplicationResource.java
@@ -98,22 +98,28 @@ public class URLApplicationResource extends PostfixedApplicationResource {
         return Collections.unmodifiableSet(remoteProtocols);
     }
 
-    private static boolean isLocal(URL url) {
+    private boolean isLocal(URL url) {
         return !REMOTE_PROTOCOLS.contains(url.getProtocol());
     }
 
-    /** the URL where the contents can be found. */
+    /**
+     * the URL where the contents can be found.
+     */
     private final URL url;
-    /** if the URL matches a file, this is the file. */
+    /**
+     * if the URL matches a file, this is the file.
+     */
     private File file;
-    /** if the URL points to a local resource */
+    /**
+     * if the URL points to a local resource
+     */
     private final boolean local;
 
     /**
      * Creates a URLApplicationResource for the specified path that can be accessed through the specified URL.
      *
      * @param localePath the path including localization.
-     * @param url the URL where the contents can be found.
+     * @param url        the URL where the contents can be found.
      */
     public URLApplicationResource(String localePath, URL url) {
         super(localePath);
@@ -127,9 +133,9 @@ public class URLApplicationResource extends PostfixedApplicationResource {
     /**
      * Creates a URLApplicationResource for the specified path that can be accessed through the specified URL.
      *
-     * @param path the path excluding localization.
+     * @param path   the path excluding localization.
      * @param locale the Locale.
-     * @param url the URL where the contents can be found.
+     * @param url    the URL where the contents can be found.
      */
     public URLApplicationResource(String path, Locale locale, URL url) {
         super(path, locale);
@@ -156,7 +162,7 @@ public class URLApplicationResource extends PostfixedApplicationResource {
         }
     }
 
-    private static File getFile(URL url) {
+    private File getFile(URL url) {
         try {
             return new File(new URI(url.toExternalForm()).getSchemeSpecificPart());
         } catch (URISyntaxException e) {
@@ -165,17 +171,25 @@ public class URLApplicationResource extends PostfixedApplicationResource {
         }
     }
 
-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public InputStream getInputStream() throws IOException {
         if (file != null) {
-            return Files.newInputStream(file.toPath());
+            if (file.exists()) {
+                return Files.newInputStream(file.toPath());
+            } else {
+                throw new FileNotFoundException("File does not exist: " + file);
+            }
         } else {
             return openConnection().getInputStream();
         }
     }
 
-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public long getLastModified() throws IOException {
         if (file != null) {
@@ -190,7 +204,9 @@ public class URLApplicationResource extends PostfixedApplicationResource {
         }
     }
 
-    /** {@inheritDoc} */
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public String toString() {
         return "Resource " + getLocalePath() + " at " + url.toString();
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/AbstractClientRequestTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/AbstractClientRequestTest.java
new file mode 100644
index 000000000..338270d11
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/AbstractClientRequestTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.tiles.request;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createMockBuilder;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link AbstractClientRequest}.
+ */
+public class AbstractClientRequestTest {
+
+    /**
+     * The request to test.
+     */
+    private AbstractClientRequest request;
+
+    /**
+     * The application context.
+     */
+    private ApplicationContext applicationContext;
+
+    /**
+     * The application scope.
+     */
+    private Map<String, Object> applicationScope;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        applicationContext = createMock(ApplicationContext.class);
+        applicationScope = new HashMap<>();
+        request = createMockBuilder(AbstractClientRequest.class).withConstructor(applicationContext).createMock();
+
+        expect(applicationContext.getApplicationScope()).andReturn(applicationScope).anyTimes();
+    }
+
+    /**
+     * Test method for {@link AbstractClientRequest#dispatch(String)}.
+     *
+     * @throws IOException If something goes wrong.
+     */
+    @Test
+    public void testDispatch() throws IOException {
+        Map<String, Object> requestScope = new HashMap<>();
+
+        expect(request.getContext(Request.REQUEST_SCOPE)).andReturn(requestScope).anyTimes();
+        request.doForward("/my/path.html");
+        request.doInclude("/my/path2.html");
+
+        replay(request, applicationContext);
+        request.dispatch("/my/path.html");
+        request.dispatch("/my/path2.html");
+        verify(request, applicationContext);
+    }
+
+    /**
+     * Test method for {@link AbstractClientRequest#include(String)}.
+     *
+     * @throws IOException If something goes wrong.
+     */
+    @Test
+    public void testInclude() throws IOException {
+        Map<String, Object> requestScope = new HashMap<>();
+
+        expect(request.getContext(Request.REQUEST_SCOPE)).andReturn(requestScope).anyTimes();
+        request.doInclude("/my/path2.html");
+
+        replay(request, applicationContext);
+        request.include("/my/path2.html");
+        assertTrue((Boolean) request.getContext(Request.REQUEST_SCOPE).get(AbstractRequest.FORCE_INCLUDE_ATTRIBUTE_NAME));
+        verify(request, applicationContext);
+    }
+
+    /**
+     * Test method for {@link AbstractClientRequest#getApplicationContext()}.
+     */
+    @Test
+    public void testGetApplicationContext() {
+        replay(request, applicationContext);
+        assertEquals(applicationContext, request.getApplicationContext());
+        verify(request, applicationContext);
+    }
+
+    /**
+     * Test method for {@link AbstractClientRequest#getContext(String)}.
+     */
+    @Test
+    public void testGetContext() {
+        Map<String, Object> scope = createMock(Map.class);
+
+        expect(request.getContext("myScope")).andReturn(scope);
+
+        replay(request, applicationContext, scope);
+        assertEquals(scope, request.getContext("myScope"));
+        verify(request, applicationContext, scope);
+    }
+
+    /**
+     * Test method for {@link AbstractClientRequest#getAvailableScopes()}.
+     */
+    @Test
+    public void testGetAvailableScopes() {
+        String[] scopes = new String[]{"one", "two", "three"};
+
+        expect(request.getAvailableScopes()).andReturn(Arrays.asList(scopes));
+
+        replay(request, applicationContext);
+        assertArrayEquals(scopes, request.getAvailableScopes().toArray());
+        verify(request, applicationContext);
+    }
+
+    /**
+     * Test method for {@link AbstractClientRequest#getApplicationScope()}.
+     */
+    @Test
+    public void testGetApplicationScope() {
+        replay(request, applicationContext);
+        assertEquals(applicationScope, request.getApplicationScope());
+        verify(request, applicationContext);
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/AbstractRequestTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/AbstractRequestTest.java
new file mode 100644
index 000000000..e0e3dd0c3
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/AbstractRequestTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.tiles.request;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.easymock.EasyMock.createMockBuilder;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link AbstractRequest}.
+ */
+public class AbstractRequestTest {
+
+    /**
+     * Test method for {@link AbstractRequest#setForceInclude(boolean)}.
+     */
+    @Test
+    public void testSetForceInclude() {
+        AbstractRequest request = createMockBuilder(AbstractRequest.class).createMock();
+        Map<String, Object> scope = new HashMap<>();
+
+        expect(request.getContext(Request.REQUEST_SCOPE)).andReturn(scope).anyTimes();
+
+        replay(request);
+        assertFalse(request.isForceInclude());
+        request.setForceInclude(true);
+        assertTrue(request.isForceInclude());
+        verify(request);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/AbstractViewRequestTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/AbstractViewRequestTest.java
new file mode 100644
index 000000000..bf650240d
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/AbstractViewRequestTest.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.tiles.request;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createMockBuilder;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link AbstractViewRequest}.
+ */
+public class AbstractViewRequestTest {
+
+    /**
+     * The request to test.
+     */
+    private AbstractViewRequest request;
+
+    /**
+     * The internal request.
+     */
+    private DispatchRequest wrappedRequest;
+
+    /**
+     * The application context.
+     */
+    private ApplicationContext applicationContext;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        wrappedRequest = createMock(DispatchRequest.class);
+        request = createMockBuilder(AbstractViewRequest.class).withConstructor(wrappedRequest).createMock();
+        applicationContext = createMock(ApplicationContext.class);
+        Map<String, Object> applicationScope = new HashMap<>();
+
+        expect(wrappedRequest.getApplicationContext()).andReturn(applicationContext).anyTimes();
+        expect(applicationContext.getApplicationScope()).andReturn(applicationScope).anyTimes();
+    }
+
+    /**
+     * Test method for {@link AbstractViewRequest#dispatch(String)}.
+     *
+     * @throws IOException If something goes wrong.
+     */
+    @Test
+    public void testDispatch() throws IOException {
+        Map<String, Object> requestScope = new HashMap<>();
+
+        expect(request.getContext(Request.REQUEST_SCOPE)).andReturn(requestScope);
+        wrappedRequest.include("/my/path.html");
+
+        replay(wrappedRequest, request, applicationContext);
+        request.dispatch("/my/path.html");
+        assertTrue((Boolean) requestScope.get(AbstractRequest.FORCE_INCLUDE_ATTRIBUTE_NAME));
+        verify(wrappedRequest, request, applicationContext);
+    }
+
+    /**
+     * Test method for {@link AbstractViewRequest#include(String)}.
+     *
+     * @throws IOException If something goes wrong.
+     */
+    @Test
+    public void testInclude() throws IOException {
+        Map<String, Object> requestScope = new HashMap<>();
+
+        expect(request.getContext(Request.REQUEST_SCOPE)).andReturn(requestScope);
+        wrappedRequest.include("/my/path.html");
+
+        replay(wrappedRequest, request, applicationContext);
+        request.include("/my/path.html");
+        assertTrue((Boolean) requestScope.get(AbstractRequest.FORCE_INCLUDE_ATTRIBUTE_NAME));
+        verify(wrappedRequest, request, applicationContext);
+    }
+
+    /**
+     * Test method for {@link AbstractViewRequest#doInclude(String)}.
+     *
+     * @throws IOException If something goes wrong.
+     */
+    @Test
+    public void testDoInclude() throws IOException {
+        wrappedRequest.include("/my/path.html");
+
+        replay(wrappedRequest, request, applicationContext);
+        request.doInclude("/my/path.html");
+        verify(wrappedRequest, request, applicationContext);
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/ApplicationAccessTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/ApplicationAccessTest.java
new file mode 100644
index 000000000..08b5ec27f
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/ApplicationAccessTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.tiles.request;
+
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+/**
+ * Tests {@link ApplicationAccess}.
+ */
+public class ApplicationAccessTest {
+
+    /**
+     * Test method for {@link ApplicationAccess#register(ApplicationContext)}.
+     */
+    @Test
+    public void testRegister() {
+        ApplicationContext applicationContext = createMock(ApplicationContext.class);
+        Map<String, Object> applicationScope = createMock(Map.class);
+
+        expect(applicationContext.getApplicationScope()).andReturn(applicationScope);
+        expect(applicationScope.put(ApplicationAccess.APPLICATION_CONTEXT_ATTRIBUTE, applicationContext)).andReturn(null);
+
+        replay(applicationContext, applicationScope);
+        ApplicationAccess.register(applicationContext);
+        verify(applicationContext, applicationScope);
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/DispatchRequestWrapperTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/DispatchRequestWrapperTest.java
new file mode 100644
index 000000000..2fa6a96d0
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/DispatchRequestWrapperTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.tiles.request;
+
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+/**
+ * Tests {@link DispatchRequestWrapper}.
+ */
+public class DispatchRequestWrapperTest {
+
+    protected DispatchRequest createMockRequest() {
+        return createMock(DispatchRequest.class);
+    }
+
+    protected DispatchRequestWrapper createRequestWrapper(Request wrappedRequest) {
+        return new DispatchRequestWrapper((DispatchRequest) wrappedRequest);
+    }
+
+    /**
+     * Test method for {@link DispatchRequestWrapper#dispatch(String)}.
+     *
+     * @throws IOException If something goes wrong.
+     */
+    @Test
+    public void testDispatch() throws IOException {
+        DispatchRequest wrappedRequest = createMockRequest();
+
+        wrappedRequest.dispatch("/my/path.html");
+
+        replay(wrappedRequest);
+        DispatchRequestWrapper request = createRequestWrapper(wrappedRequest);
+        request.dispatch("/my/path.html");
+        verify(wrappedRequest);
+    }
+
+    /**
+     * Test method for {@link DispatchRequestWrapper#include(String)}.
+     *
+     * @throws IOException If something goes wrong.
+     */
+    @Test
+    public void testInclude() throws IOException {
+        DispatchRequest wrappedRequest = createMockRequest();
+
+        wrappedRequest.include("/my/path.html");
+
+        replay(wrappedRequest);
+        DispatchRequestWrapper request = createRequestWrapper(wrappedRequest);
+        request.include("/my/path.html");
+        verify(wrappedRequest);
+    }
+
+    /**
+     * Test method for {@link DispatchRequestWrapper#setContentType(String)}.
+     */
+    @Test
+    public void testSetContentType() {
+        DispatchRequest wrappedRequest = createMockRequest();
+
+        wrappedRequest.setContentType("text/html");
+
+        replay(wrappedRequest);
+        DispatchRequestWrapper request = createRequestWrapper(wrappedRequest);
+        request.setContentType("text/html");
+        verify(wrappedRequest);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/NotAvailableFeatureExceptionTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/NotAvailableFeatureExceptionTest.java
new file mode 100644
index 000000000..f1d6caf51
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/NotAvailableFeatureExceptionTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.tiles.request;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests {@link NotAvailableFeatureException}.
+ */
+public class NotAvailableFeatureExceptionTest {
+
+    /**
+     * Test method for {@link NotAvailableFeatureException#NotAvailableFeatureException(String, Throwable)}.
+     */
+    @Test
+    public void testNotAvailableFeatureExceptionStringThrowable() {
+        Throwable cause = new Throwable();
+        NotAvailableFeatureException exception = new NotAvailableFeatureException("my message", cause);
+        assertEquals("my message", exception.getMessage());
+        assertEquals(cause, exception.getCause());
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/RequestExceptionTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/RequestExceptionTest.java
new file mode 100644
index 000000000..b8a699c45
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/RequestExceptionTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.tiles.request;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Tests {@link RequestException}.
+ */
+public class RequestExceptionTest {
+
+    /**
+     * Test method for {@link RequestException#RequestException(String)}.
+     */
+    @Test
+    public void testRequestExceptionString() {
+        RequestException exception = new RequestException("my message");
+        assertEquals("my message", exception.getMessage());
+        assertNull(exception.getCause());
+    }
+
+    /**
+     * Test method for {@link RequestException#RequestException(String, Throwable)}.
+     */
+    @Test
+    public void testRequestExceptionStringThrowable() {
+        Throwable cause = new Throwable();
+        RequestException exception = new RequestException("my message", cause);
+        assertEquals("my message", exception.getMessage());
+        assertEquals(cause, exception.getCause());
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/AddableParameterMapTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/AddableParameterMapTest.java
new file mode 100644
index 000000000..a06d9bb81
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/AddableParameterMapTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.apache.tiles.request.attribute.HasAddableKeys;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Tests {@link AddableParameterMap}.
+ */
+public class AddableParameterMapTest {
+
+    /**
+     * The object to test.
+     */
+    private AddableParameterMap map;
+
+    /**
+     * The extractor to use.
+     */
+    private HasAddableKeys<String> extractor;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        extractor = createMock(HasAddableKeys.class);
+        map = new AddableParameterMap(extractor);
+    }
+
+    /**
+     * Test method for {@link AddableParameterMap#entrySet()}.
+     */
+    @Test
+    public void testEntrySet() {
+        Set<Map.Entry<String, String>> entrySet = map.entrySet();
+        MapEntry<String, String> entry1 = new MapEntry<>("one", "value1", false);
+        MapEntry<String, String> entry2 = new MapEntry<>("two", "value2", false);
+        List<Map.Entry<String, String>> entries = new ArrayList<>(2);
+        entries.add(entry1);
+        entries.add(entry2);
+
+        extractor.setValue("one", "value1");
+        expectLastCall().times(2);
+        extractor.setValue("two", "value2");
+        replay(extractor);
+        entrySet.add(entry1);
+        entrySet.addAll(entries);
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link AddableParameterMap#put(String, String)}.
+     */
+    @Test
+    public void testPut() {
+        expect(extractor.getValue("one")).andReturn(null);
+        extractor.setValue("one", "value1");
+
+        replay(extractor);
+        assertNull(map.put("one", "value1"));
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link AddableParameterMap#putAll(Map)}.
+     */
+    @Test
+    public void testPutAll() {
+        Map<String, String> map = new HashMap<>();
+        map.put("one", "value1");
+        map.put("two", "value2");
+
+        extractor.setValue("one", "value1");
+        extractor.setValue("two", "value2");
+
+        replay(extractor);
+        this.map.putAll(map);
+        verify(extractor);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/CollectionUtilTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/CollectionUtilTest.java
new file mode 100644
index 000000000..39f286760
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/CollectionUtilTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.junit.Test;
+
+import java.util.Enumeration;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+
+public class CollectionUtilTest {
+
+    @Test
+    public void testKey() {
+        assertEquals("1", CollectionUtil.key(1));
+        assertEquals("hello", CollectionUtil.key("hello"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testKeyException() {
+        CollectionUtil.key(null);
+    }
+
+    @Test
+    public void testEnumerationSize() {
+        Enumeration<Object> enumeration = createMock(Enumeration.class);
+
+        expect(enumeration.hasMoreElements()).andReturn(true);
+        expect(enumeration.nextElement()).andReturn(1);
+        expect(enumeration.hasMoreElements()).andReturn(true);
+        expect(enumeration.nextElement()).andReturn(1);
+        expect(enumeration.hasMoreElements()).andReturn(false);
+
+        replay(enumeration);
+        assertEquals(2, CollectionUtil.enumerationSize(enumeration));
+        verify(enumeration);
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/HeaderValuesCollectionTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/HeaderValuesCollectionTest.java
new file mode 100644
index 000000000..8a4069171
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/HeaderValuesCollectionTest.java
@@ -0,0 +1,398 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.apache.tiles.request.attribute.EnumeratedValuesExtractor;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link HeaderValuesMap#values()}.
+ */
+public class HeaderValuesCollectionTest {
+
+
+    /**
+     * The extractor to use.
+     */
+    private EnumeratedValuesExtractor extractor;
+
+    /**
+     * The map to test.
+     */
+    private HeaderValuesMap map;
+
+    /**
+     * The collection.
+     */
+    private Collection<String[]> coll;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        extractor = createMock(EnumeratedValuesExtractor.class);
+        map = new HeaderValuesMap(extractor);
+        coll = map.values();
+    }
+
+    /**
+     * Tests {@link Collection#add(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAdd() {
+        coll.add(null);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAddAll() {
+        coll.addAll(null);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClear() {
+        coll.clear();
+    }
+
+    /**
+     * Tests {@link Collection#contains(Object)}.
+     */
+    @Test
+    public void testContainsValue() {
+        assertFalse(map.containsValue(1));
+
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys, values1, values2);
+        assertTrue(coll.contains(new String[]{"value2", "value3"}));
+        verify(extractor, keys, values1, values2);
+    }
+
+    /**
+     * Tests {@link Collection#contains(Object)}.
+     */
+    @Test
+    public void testContainsValueFalse() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys, values1, values2);
+        assertFalse(coll.contains(new String[]{"value2", "value4"}));
+        verify(extractor, keys, values1, values2);
+    }
+
+    @Test
+    public void testContainsAll() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys).times(2);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+
+        expect(extractor.getValues("one")).andReturn(values1).times(2);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys, values1, values2);
+        List<String[]> coll = new ArrayList<>();
+        coll.add(new String[]{"value1"});
+        coll.add(new String[]{"value2", "value3"});
+        assertTrue(this.coll.containsAll(coll));
+        verify(extractor, keys, values1, values2);
+    }
+
+    @Test
+    public void testContainsAllFalse() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+
+        replay(extractor, keys, values1, values2);
+        List<String[]> coll = new ArrayList<>();
+        coll.add(new String[]{"value4"});
+        assertFalse(this.coll.containsAll(coll));
+        verify(extractor, keys, values1, values2);
+    }
+
+    /**
+     * Test method for {@link Collection#isEmpty()}.
+     */
+    @Test
+    public void testIsEmpty() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+
+        replay(extractor, keys);
+        assertFalse(coll.isEmpty());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Collection#iterator()}.
+     */
+    @Test
+    public void testIterator() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys, values2);
+        Iterator<String[]> entryIt = coll.iterator();
+        assertTrue(entryIt.hasNext());
+        assertArrayEquals(new String[]{"value2", "value3"}, entryIt.next());
+        verify(extractor, keys, values2);
+    }
+
+    /**
+     * Test method for {@link Collection#iterator()}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testIteratorRemove() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+
+        try {
+            replay(extractor, keys);
+            coll.iterator().remove();
+        } finally {
+            verify(extractor, keys);
+        }
+    }
+
+    /**
+     * Tests {@link Collection#remove(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemove() {
+        coll.remove(null);
+    }
+
+    /**
+     * Tests {@link Collection#removeAll(Collection)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemoveAll() {
+        coll.removeAll(null);
+    }
+
+    /**
+     * Tests {@link Collection#retainAll(Collection)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRetainAll() {
+        coll.retainAll(null);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#size()}.
+     */
+    @Test
+    public void testSize() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys);
+        assertEquals(2, coll.size());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Collection#toArray()}.
+     */
+    @Test
+    public void testToArray() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        String[][] entryArray = new String[2][];
+        entryArray[0] = new String[]{"value1"};
+        entryArray[1] = new String[]{"value2", "value3"};
+
+        replay(extractor, keys, values1, values2);
+        assertArrayEquals(entryArray, coll.toArray());
+        verify(extractor, keys, values1, values2);
+    }
+
+    /**
+     * Test method for {@link Collection#toArray(Object[])}.
+     */
+    @Test
+    public void testToArrayTArray() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        String[][] entryArray = new String[2][];
+        entryArray[0] = new String[]{"value1"};
+        entryArray[1] = new String[]{"value2", "value3"};
+        String[][] realArray = new String[2][];
+
+        replay(extractor, keys, values1, values2);
+        assertArrayEquals(entryArray, coll.toArray(realArray));
+        verify(extractor, keys, values1, values2);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/HeaderValuesMapEntrySetTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/HeaderValuesMapEntrySetTest.java
new file mode 100644
index 000000000..344151c52
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/HeaderValuesMapEntrySetTest.java
@@ -0,0 +1,337 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.apache.tiles.request.attribute.EnumeratedValuesExtractor;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link HeaderValuesMap entry set}.
+ */
+public class HeaderValuesMapEntrySetTest {
+
+    /**
+     * The extractor to use.
+     */
+    private EnumeratedValuesExtractor extractor;
+
+    /**
+     * The set to test.
+     */
+    private Set<Map.Entry<String, String[]>> entrySet;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        extractor = createMock(EnumeratedValuesExtractor.class);
+        HeaderValuesMap map = new HeaderValuesMap(extractor);
+        entrySet = map.entrySet();
+    }
+
+    /**
+     * Tests {@link Set#add(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAdd() {
+        entrySet.add(null);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAddAll() {
+        entrySet.addAll(null);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClear() {
+        entrySet.clear();
+    }
+
+    /**
+     * Tests {@link Set#contains(Object)}.
+     */
+    @Test
+    public void testContains() {
+        Map.Entry<String, String[]> entry = createMock(Map.Entry.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(entry.getKey()).andReturn("two");
+        expect(entry.getValue()).andReturn(new String[]{"value2", "value3"});
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        replay(extractor, entry, values2);
+        assertTrue(entrySet.contains(entry));
+        verify(extractor, entry, values2);
+    }
+
+    @Test
+    public void testContainsAll() {
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+        Map.Entry<String, String[]> entry1 = createMock(Map.Entry.class);
+        Map.Entry<String, String[]> entry2 = createMock(Map.Entry.class);
+
+        expect(entry1.getKey()).andReturn("one");
+        expect(entry1.getValue()).andReturn(new String[]{"value1"});
+        expect(entry2.getKey()).andReturn("two");
+        expect(entry2.getValue()).andReturn(new String[]{"value2", "value3"});
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        replay(extractor, values1, values2, entry1, entry2);
+        List<Map.Entry<String, String[]>> coll = new ArrayList<>();
+        coll.add(entry1);
+        coll.add(entry2);
+        assertTrue(entrySet.containsAll(coll));
+        verify(extractor, values1, values2, entry1, entry2);
+    }
+
+    @Test
+    public void testContainsAllFalse() {
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Map.Entry<String, String[]> entry1 = createMock(Map.Entry.class);
+
+        expect(entry1.getKey()).andReturn("one");
+        expect(entry1.getValue()).andReturn(new String[]{"value4"});
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+
+        replay(extractor, values1, entry1);
+        List<Map.Entry<String, String[]>> coll = new ArrayList<>();
+        coll.add(entry1);
+        assertFalse(entrySet.containsAll(coll));
+        verify(extractor, values1, entry1);
+    }
+
+    /**
+     * Test method for {@link Set#isEmpty()}.
+     */
+    @Test
+    public void testIsEmpty() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+
+        replay(extractor, keys);
+        assertFalse(entrySet.isEmpty());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Set#iterator()}.
+     */
+    @Test
+    public void testIterator() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys, values2);
+        Iterator<Map.Entry<String, String[]>> entryIt = entrySet.iterator();
+        assertTrue(entryIt.hasNext());
+        MapEntryArrayValues<String, String> entry = new MapEntryArrayValues<>(
+            "two", new String[]{"value2", "value3"}, false);
+        assertEquals(entry, entryIt.next());
+        verify(extractor, keys, values2);
+    }
+
+    /**
+     * Test method for {@link Set#iterator()}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testIteratorRemove() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+
+        try {
+            replay(extractor, keys);
+            entrySet.iterator().remove();
+        } finally {
+            verify(extractor, keys);
+        }
+    }
+
+    /**
+     * Tests {@link Set#remove(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemove() {
+        entrySet.remove(null);
+    }
+
+    /**
+     * Tests {@link Set#removeAll(java.util.Collection)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemoveAll() {
+        entrySet.removeAll(null);
+    }
+
+    /**
+     * Tests {@link Set#retainAll(java.util.Collection)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRetainAll() {
+        entrySet.retainAll(null);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#size()}.
+     */
+    @Test
+    public void testSize() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys);
+        assertEquals(2, entrySet.size());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Set#toArray()}.
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testToArray() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        MapEntryArrayValues<String, String>[] entryArray = new MapEntryArrayValues[2];
+        entryArray[0] = new MapEntryArrayValues<>("one", new String[]{"value1"}, false);
+        entryArray[1] = new MapEntryArrayValues<>("two", new String[]{"value2", "value3"}, false);
+
+        replay(extractor, keys, values1, values2);
+        assertArrayEquals(entryArray, entrySet.toArray());
+        verify(extractor, keys, values1, values2);
+    }
+
+    /**
+     * Test method for {@link Set#toArray(Object[])}.
+     */
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testToArrayTArray() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        MapEntryArrayValues<String, String>[] entryArray = new MapEntryArrayValues[2];
+        entryArray[0] = new MapEntryArrayValues<>("one", new String[]{"value1"}, false);
+        entryArray[1] = new MapEntryArrayValues<>("two", new String[]{"value2", "value3"}, false);
+        MapEntryArrayValues<String, String>[] realArray = new MapEntryArrayValues[2];
+
+        replay(extractor, keys, values1, values2);
+        assertArrayEquals(entryArray, entrySet.toArray(realArray));
+        verify(extractor, keys, values1, values2);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/HeaderValuesMapTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/HeaderValuesMapTest.java
new file mode 100644
index 000000000..aaf0ef602
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/HeaderValuesMapTest.java
@@ -0,0 +1,337 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.apache.tiles.request.attribute.EnumeratedValuesExtractor;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link HeaderValuesMap}.
+ */
+public class HeaderValuesMapTest {
+
+    /**
+     * The extractor to use.
+     */
+    private EnumeratedValuesExtractor extractor;
+
+    /**
+     * The map to test.
+     */
+    private HeaderValuesMap map;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        extractor = createMock(EnumeratedValuesExtractor.class);
+        map = new HeaderValuesMap(extractor);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#hashCode()}.
+     */
+    @Test
+    public void testHashCode() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys, values1, values2);
+        assertEquals(
+            ("one".hashCode() ^ "value1".hashCode())
+                + ("two".hashCode() ^ ("value2".hashCode() + "value3"
+                .hashCode())),
+            map.hashCode());
+        verify(extractor, keys, values1, values2);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#clear()}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClear() {
+        map.clear();
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#containsKey(Object)}.
+     */
+    @Test
+    public void testContainsKey() {
+        expect(extractor.getValue("one")).andReturn("value1");
+        expect(extractor.getValue("two")).andReturn(null);
+
+        replay(extractor);
+        assertTrue(map.containsKey("one"));
+        assertFalse(map.containsKey("two"));
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#containsValue(Object)}.
+     */
+    @Test
+    public void testContainsValue() {
+        assertFalse(map.containsValue(1));
+
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys, values1, values2);
+        assertTrue(map.containsValue(new String[]{"value2", "value3"}));
+        verify(extractor, keys, values1, values2);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#containsValue(Object)}.
+     */
+    @Test
+    public void testContainsValueFalse() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys, values1, values2);
+        assertFalse(map.containsValue(new String[]{"value2", "value4"}));
+        verify(extractor, keys, values1, values2);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#equals(Object)}.
+     */
+    @Test
+    public void testEqualsObject() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+        EnumeratedValuesExtractor otherExtractor = createMock(EnumeratedValuesExtractor.class);
+        Enumeration<String> otherValues1 = createMock(Enumeration.class);
+        Enumeration<String> otherValues2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("one")).andReturn(values1);
+        expect(values1.hasMoreElements()).andReturn(true);
+        expect(values1.nextElement()).andReturn("value1");
+        expect(values1.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        expect(otherExtractor.getValues("one")).andReturn(otherValues1);
+        expect(otherValues1.hasMoreElements()).andReturn(true);
+        expect(otherValues1.nextElement()).andReturn("value1");
+        expect(otherValues1.hasMoreElements()).andReturn(false);
+
+        expect(otherExtractor.getValues("two")).andReturn(otherValues2);
+        expect(otherValues2.hasMoreElements()).andReturn(true);
+        expect(otherValues2.nextElement()).andReturn("value2");
+        expect(otherValues2.hasMoreElements()).andReturn(true);
+        expect(otherValues2.nextElement()).andReturn("value3");
+        expect(otherValues2.hasMoreElements()).andReturn(false);
+
+        replay(extractor, otherExtractor, keys, values1, values2, otherValues1, otherValues2);
+        HeaderValuesMap otherMap = new HeaderValuesMap(otherExtractor);
+        assertTrue(map.equals(otherMap));
+        verify(extractor, otherExtractor, keys, values1, values2, otherValues1, otherValues2);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#get(Object)}.
+     */
+    @Test
+    public void testGet() {
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getValues("two")).andReturn(values2);
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value2");
+        expect(values2.hasMoreElements()).andReturn(true);
+        expect(values2.nextElement()).andReturn("value3");
+        expect(values2.hasMoreElements()).andReturn(false);
+
+        replay(extractor, values2);
+        assertArrayEquals(new String[]{"value2", "value3"}, map.get("two"));
+        verify(extractor, values2);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#isEmpty()}.
+     */
+    @Test
+    public void testIsEmpty() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+
+        replay(extractor, keys);
+        assertFalse(map.isEmpty());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#isEmpty()}.
+     */
+    @Test
+    public void testIsEmptyTrue() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys);
+        assertTrue(map.isEmpty());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#keySet()}.
+     */
+    @Test
+    public void testKeySet() {
+        replay(extractor);
+        assertTrue(map.keySet() instanceof KeySet);
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#put(String, String[])}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testPut() {
+        map.put("one", new String[]{"value1", "value2"});
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#putAll(java.util.Map)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testPutAll() {
+        map.putAll(new HashMap<>());
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#remove(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemove() {
+        map.remove("one");
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#size()}.
+     */
+    @Test
+    public void testSize() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys);
+        assertEquals(2, map.size());
+        verify(extractor, keys);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/KeySetTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/KeySetTest.java
new file mode 100644
index 000000000..f8fe60a31
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/KeySetTest.java
@@ -0,0 +1,283 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.apache.tiles.request.attribute.HasKeys;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link KeySet}.
+ */
+public class KeySetTest {
+
+
+    /**
+     * The extractor to use.
+     */
+    private HasKeys<Integer> extractor;
+
+    /**
+     * The key set.
+     */
+    private Set<String> entrySet;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        extractor = createMock(HasKeys.class);
+        entrySet = new KeySet(extractor);
+    }
+
+    /**
+     * Tests {@link Set#add(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAdd() {
+        entrySet.add(null);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAddAll() {
+        entrySet.addAll(null);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClear() {
+        entrySet.clear();
+    }
+
+    /**
+     * Tests {@link Set#contains(Object)}.
+     */
+    @Test
+    public void testContains() {
+        expect(extractor.getValue("one")).andReturn(1);
+
+        replay(extractor);
+        assertTrue(entrySet.contains("one"));
+        verify(extractor);
+    }
+
+    /**
+     * Tests {@link Set#contains(Object)}.
+     */
+    @Test
+    public void testContainsFalse() {
+        expect(extractor.getValue("one")).andReturn(null);
+
+        replay(extractor);
+        assertFalse(entrySet.contains("one"));
+        verify(extractor);
+    }
+
+    @Test
+    public void testContainsAll() {
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(1);
+
+        replay(extractor);
+        List<String> coll = new ArrayList<String>();
+        coll.add("one");
+        coll.add("two");
+        assertTrue(entrySet.containsAll(coll));
+        verify(extractor);
+    }
+
+    @Test
+    public void testContainsAllFalse() {
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(null);
+
+        replay(extractor);
+        List<String> coll = new ArrayList<>();
+        coll.add("one");
+        coll.add("two");
+        assertFalse(entrySet.containsAll(coll));
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link Set#isEmpty()}.
+     */
+    @Test
+    public void testIsEmpty() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+
+        replay(extractor, keys);
+        assertFalse(entrySet.isEmpty());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Set#isEmpty()}.
+     */
+    @Test
+    public void testIsEmptyTrue() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys);
+        assertTrue(entrySet.isEmpty());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Set#iterator()}.
+     */
+    @Test
+    public void testIterator() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+
+        replay(extractor, keys, values2);
+        Iterator<String> entryIt = entrySet.iterator();
+        assertTrue(entryIt.hasNext());
+        assertEquals("two", entryIt.next());
+        verify(extractor, keys, values2);
+    }
+
+    /**
+     * Test method for {@link Set#iterator()}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testIteratorRemove() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+
+        try {
+            replay(extractor, keys);
+            entrySet.iterator().remove();
+        } finally {
+            verify(extractor, keys);
+        }
+    }
+
+    /**
+     * Tests {@link Set#remove(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemove() {
+        entrySet.remove(null);
+    }
+
+    /**
+     * Tests {@link Set#removeAll(java.util.Collection)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemoveAll() {
+        entrySet.removeAll(null);
+    }
+
+    /**
+     * Tests {@link Set#retainAll(java.util.Collection)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRetainAll() {
+        entrySet.retainAll(null);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#size()}.
+     */
+    @Test
+    public void testSize() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys);
+        assertEquals(2, entrySet.size());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Set#toArray()}.
+     */
+    @Test
+    public void testToArray() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+        replay(extractor, keys, values1, values2);
+        assertArrayEquals(new String[]{"one", "two"}, entrySet.toArray());
+        verify(extractor, keys, values1, values2);
+    }
+
+    /**
+     * Test method for {@link Set#toArray(Object[])}.
+     */
+    @Test
+    public void testToArrayTArray() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys, values1, values2);
+        String[] realArray = new String[2];
+        assertArrayEquals(new String[]{"one", "two"}, entrySet.toArray(realArray));
+        verify(extractor, keys, values1, values2);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/MapEntryArrayValuesTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/MapEntryArrayValuesTest.java
new file mode 100644
index 000000000..97045cd10
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/MapEntryArrayValuesTest.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.tiles.request.collection;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+/**
+ * Tests {@link MapEntryArrayValues}.
+ */
+public class MapEntryArrayValuesTest {
+
+    /**
+     * Test method for {@link MapEntryArrayValues#hashCode()}.
+     */
+    @Test
+    public void testHashCode() {
+        MapEntryArrayValues<String, String> entry = new MapEntryArrayValues<>("key", new String[]{"value1", "value2"}, false);
+        assertEquals("key".hashCode() ^ ("value1".hashCode() + "value2".hashCode()), entry.hashCode());
+        entry = new MapEntryArrayValues<>(null, new String[]{"value1", "value2"}, false);
+        assertEquals(("value1".hashCode() + "value2".hashCode()), entry.hashCode());
+        entry = new MapEntryArrayValues<>("key", null, false);
+        assertEquals("key".hashCode(), entry.hashCode());
+        entry = new MapEntryArrayValues<>(null, null, false);
+        assertEquals(0, entry.hashCode());
+    }
+
+    /**
+     * Test method for {@link MapEntryArrayValues#equals(Object)}.
+     */
+    @Test
+    public void testEqualsObject() {
+        MapEntryArrayValues<String, String> entry = new MapEntryArrayValues<>("key", new String[]{"value1", "value2"}, false);
+        assertNotEquals(null, entry);
+        MapEntryArrayValues<String, String> entry2 = new MapEntryArrayValues<>("key", new String[]{"value1", "value2"}, false);
+        assertEquals(entry, entry2);
+        entry2 = new MapEntryArrayValues<>("key", null, false);
+        assertNotEquals(entry, entry2);
+        entry2 = new MapEntryArrayValues<>("key2", new String[]{"value1", "value2"}, false);
+        assertNotEquals(entry, entry2);
+        entry2 = new MapEntryArrayValues<>("key", new String[]{"value1", "value3"}, false);
+        assertNotEquals(entry, entry2);
+        entry = new MapEntryArrayValues<>(null, new String[]{"value1", "value2"}, false);
+        entry2 = new MapEntryArrayValues<>(null, new String[]{"value1", "value2"}, false);
+        assertEquals(entry, entry2);
+        entry = new MapEntryArrayValues<>("key", null, false);
+        entry2 = new MapEntryArrayValues<>("key", null, false);
+        assertEquals(entry, entry2);
+        entry2 = new MapEntryArrayValues<>("key", new String[]{"value1", "value2"}, false);
+        assertNotEquals(entry, entry2);
+        entry = new MapEntryArrayValues<>(null, new String[]{null, "value2"}, false);
+        entry2 = new MapEntryArrayValues<>(null, new String[]{null, "value2"}, false);
+        assertEquals(entry, entry2);
+        entry2 = new MapEntryArrayValues<>(null, new String[]{"value1", "value2"}, false);
+        assertNotEquals(entry, entry2);
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/MapEntryTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/MapEntryTest.java
new file mode 100644
index 000000000..31406b057
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/MapEntryTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+/**
+ * Tests {@link MapEntry}.
+ */
+public class MapEntryTest {
+
+    /**
+     * Test method for {@link MapEntry#hashCode()}.
+     */
+    @Test
+    public void testHashCode() {
+        MapEntry<String, String> entry = new MapEntry<>("key", "value", false);
+        assertEquals("key".hashCode() ^ "value".hashCode(), entry.hashCode());
+        entry = new MapEntry<>(null, "value", false);
+        assertEquals("value".hashCode(), entry.hashCode());
+        entry = new MapEntry<>("key", null, false);
+        assertEquals("key".hashCode(), entry.hashCode());
+        entry = new MapEntry<>(null, null, false);
+        assertEquals(0, entry.hashCode());
+    }
+
+    /**
+     * Test method for {@link MapEntry#getKey()}.
+     */
+    @Test
+    public void testGetKey() {
+        MapEntry<String, String> entry = new MapEntry<>("key", "value", false);
+        assertEquals("key", entry.getKey());
+    }
+
+    /**
+     * Test method for {@link MapEntry#getValue()}.
+     */
+    @Test
+    public void testGetValue() {
+        MapEntry<String, String> entry = new MapEntry<>("key", "value", false);
+        assertEquals("value", entry.getValue());
+    }
+
+    /**
+     * Test method for {@link MapEntry#setValue(Object)}.
+     */
+    @Test
+    public void testSetValue() {
+        MapEntry<String, String> entry = new MapEntry<>("key", "value", true);
+        assertEquals("value", entry.getValue());
+        entry.setValue("value2");
+        assertEquals("value2", entry.getValue());
+    }
+
+    /**
+     * Test method for {@link MapEntry#setValue(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testSetValueException() {
+        MapEntry<String, String> entry = new MapEntry<>("key", "value", false);
+        assertEquals("value", entry.getValue());
+        entry.setValue("value2");
+    }
+
+    /**
+     * Test method for {@link MapEntry#equals(Object)}.
+     */
+    @Test
+    public void testEqualsObject() {
+        MapEntry<String, String> entry = new MapEntry<>("key", "value", false);
+        assertNotEquals(null, entry);
+        MapEntry<String, String> entry2 = new MapEntry<>("key", "value", false);
+        assertEquals(entry, entry2);
+        entry2 = new MapEntry<>("key2", "value", false);
+        assertNotEquals(entry, entry2);
+        entry2 = new MapEntry<>("key", "value2", false);
+        assertNotEquals(entry, entry2);
+        entry = new MapEntry<>(null, "value", false);
+        entry2 = new MapEntry<>(null, "value", false);
+        assertEquals(entry, entry2);
+        entry = new MapEntry<>("key", null, false);
+        entry2 = new MapEntry<>("key", null, false);
+        assertEquals(entry, entry2);
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ReadOnlyEnumerationMapEntrySetTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ReadOnlyEnumerationMapEntrySetTest.java
new file mode 100644
index 000000000..a77d6bd82
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ReadOnlyEnumerationMapEntrySetTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.apache.tiles.request.attribute.HasKeys;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link ReadOnlyEnumerationMap#entrySet()}.
+ */
+public class ReadOnlyEnumerationMapEntrySetTest {
+
+    /**
+     * The extractor to use.
+     */
+    private HasKeys<Integer> extractor;
+
+    /**
+     * The set to test.
+     */
+    private Set<Map.Entry<String, Integer>> entrySet;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        extractor = createMock(HasKeys.class);
+        ReadOnlyEnumerationMap<Integer> map = new ReadOnlyEnumerationMap<>(extractor);
+        entrySet = map.entrySet();
+    }
+
+    /**
+     * Tests {@link Set#add(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAdd() {
+        entrySet.add(null);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAddAll() {
+        entrySet.addAll(null);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClear() {
+        entrySet.clear();
+    }
+
+    /**
+     * Tests {@link Set#contains(Object)}.
+     */
+    @Test
+    public void testContains() {
+        Map.Entry<String, Integer> entry = createMock(Map.Entry.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(entry.getKey()).andReturn("two");
+        expect(entry.getValue()).andReturn(2);
+
+        expect(extractor.getValue("two")).andReturn(2);
+
+        replay(extractor, entry, values2);
+        assertTrue(entrySet.contains(entry));
+        verify(extractor, entry, values2);
+    }
+
+    @Test
+    public void testContainsAll() {
+        Map.Entry<String, Integer> entry1 = createMock(Map.Entry.class);
+        Map.Entry<String, Integer> entry2 = createMock(Map.Entry.class);
+
+        expect(entry1.getKey()).andReturn("one");
+        expect(entry1.getValue()).andReturn(1);
+        expect(entry2.getKey()).andReturn("two");
+        expect(entry2.getValue()).andReturn(2);
+
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(2);
+
+        replay(extractor, entry1, entry2);
+        List<Map.Entry<String, Integer>> coll = new ArrayList<>();
+        coll.add(entry1);
+        coll.add(entry2);
+        assertTrue(entrySet.containsAll(coll));
+        verify(extractor, entry1, entry2);
+    }
+
+    @Test
+    public void testContainsAllFalse() {
+        Map.Entry<String, String> entry1 = createMock(Map.Entry.class);
+
+        expect(entry1.getKey()).andReturn("one");
+        expect(entry1.getValue()).andReturn("value4");
+
+        expect(extractor.getValue("one")).andReturn(1);
+
+        replay(extractor, entry1);
+        List<Map.Entry<String, String>> coll = new ArrayList<>();
+        coll.add(entry1);
+        assertFalse(entrySet.containsAll(coll));
+        verify(extractor, entry1);
+    }
+
+    /**
+     * Test method for {@link Set#isEmpty()}.
+     */
+    @Test
+    public void testIsEmpty() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+
+        replay(extractor, keys);
+        assertFalse(entrySet.isEmpty());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Set#iterator()}.
+     */
+    @Test
+    public void testIterator() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+
+        expect(extractor.getValue("two")).andReturn(2);
+
+        replay(extractor, keys);
+        Iterator<Map.Entry<String, Integer>> entryIt = entrySet.iterator();
+        assertTrue(entryIt.hasNext());
+        MapEntry<String, Integer> entry = new MapEntry<>("two", 2, false);
+        assertEquals(entry, entryIt.next());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Set#iterator()}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testIteratorRemove() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+
+        try {
+            replay(extractor, keys);
+            entrySet.iterator().remove();
+        } finally {
+            verify(extractor, keys);
+        }
+    }
+
+    /**
+     * Tests {@link Set#remove(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemove() {
+        entrySet.remove(null);
+    }
+
+    /**
+     * Tests {@link Set#removeAll(java.util.Collection)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemoveAll() {
+        entrySet.removeAll(null);
+    }
+
+    /**
+     * Tests {@link Set#retainAll(java.util.Collection)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRetainAll() {
+        entrySet.retainAll(null);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#size()}.
+     */
+    @Test
+    public void testSize() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys);
+        assertEquals(2, entrySet.size());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Set#toArray()}.
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testToArray() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(2);
+
+        MapEntry<String, Integer>[] entryArray = new MapEntry[2];
+        entryArray[0] = new MapEntry<>("one", 1, false);
+        entryArray[1] = new MapEntry<>("two", 2, false);
+
+        replay(extractor, keys, values1, values2);
+        assertArrayEquals(entryArray, entrySet.toArray());
+        verify(extractor, keys, values1, values2);
+    }
+
+    /**
+     * Test method for {@link Set#toArray(Object[])}.
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testToArrayTArray() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> values1 = createMock(Enumeration.class);
+        Enumeration<String> values2 = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(2);
+
+        MapEntry<String, Integer>[] entryArray = new MapEntry[2];
+        entryArray[0] = new MapEntry<>("one", 1, false);
+        entryArray[1] = new MapEntry<>("two", 2, false);
+
+        replay(extractor, keys, values1, values2);
+        MapEntry<String, String>[] realArray = new MapEntry[2];
+        assertArrayEquals(entryArray, entrySet.toArray(realArray));
+        verify(extractor, keys, values1, values2);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ReadOnlyEnumerationMapTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ReadOnlyEnumerationMapTest.java
new file mode 100644
index 000000000..be3245691
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ReadOnlyEnumerationMapTest.java
@@ -0,0 +1,306 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.apache.tiles.request.attribute.HasKeys;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.createMockBuilder;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link ReadOnlyEnumerationMap}.
+ */
+public class ReadOnlyEnumerationMapTest {
+
+    /**
+     * The extractor to use.
+     */
+    private HasKeys<Integer> extractor;
+
+    /**
+     * The map to test.
+     */
+    private ReadOnlyEnumerationMap<Integer> map;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        extractor = createMock(HasKeys.class);
+        map = new ReadOnlyEnumerationMap<>(extractor);
+    }
+
+    /**
+     * Test method for {@link ReadOnlyEnumerationMap#clear()}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClear() {
+        map.clear();
+    }
+
+    /**
+     * Test method for {@link ReadOnlyEnumerationMap#containsKey(Object)}.
+     */
+    @Test
+    public void testContainsKey() {
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(null);
+
+        replay(extractor);
+        assertTrue(map.containsKey("one"));
+        assertFalse(map.containsKey("two"));
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link ReadOnlyEnumerationMap#containsValue(Object)}.
+     */
+    @Test
+    public void testContainsValue() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(2);
+
+        replay(extractor, keys);
+        assertTrue(map.containsValue(2));
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link ReadOnlyEnumerationMap#containsValue(Object)}.
+     */
+    @Test
+    public void testContainsValueFalse() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(1);
+
+        replay(extractor, keys);
+        assertFalse(map.containsValue(3));
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link ReadOnlyEnumerationMap#get(Object)}.
+     */
+    @Test
+    public void testGet() {
+        expect(extractor.getValue("two")).andReturn(2);
+
+        replay(extractor);
+        assertEquals(new Integer(2), map.get("two"));
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link ReadOnlyEnumerationMap#isEmpty()}.
+     */
+    @Test
+    public void testIsEmpty() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+
+        replay(extractor, keys);
+        assertFalse(map.isEmpty());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link ReadOnlyEnumerationMap#isEmpty()}.
+     */
+    @Test
+    public void testIsEmptyTrue() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys);
+        assertTrue(map.isEmpty());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link ReadOnlyEnumerationMap#keySet()}.
+     */
+    @Test
+    public void testKeySet() {
+        replay(extractor);
+        assertTrue(map.keySet() instanceof KeySet);
+        verify(extractor);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testPut() {
+        map.put("one", 1);
+    }
+
+    /**
+     * Test method for {@link ReadOnlyEnumerationMap#putAll(java.util.Map)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testPutAll() {
+        map.putAll(new HashMap<>());
+    }
+
+    /**
+     * Test method for {@link ReadOnlyEnumerationMap#remove(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemove() {
+        map.remove("one");
+    }
+
+    /**
+     * Test method for {@link ReadOnlyEnumerationMap#size()}.
+     */
+    @Test
+    public void testSize() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys);
+        assertEquals(2, map.size());
+        verify(extractor, keys);
+    }
+
+    @Test
+    public void testHashCode() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("first");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("second");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        Integer value1 = 1;
+
+        expect(extractor.getValue("first")).andReturn(value1);
+        expect(extractor.getValue("second")).andReturn(null);
+
+        replay(extractor, keys);
+        assertEquals(("first".hashCode() ^ value1.hashCode()) + ("second".hashCode()), map.hashCode());
+        verify(extractor, keys);
+    }
+
+    @Test
+    public void testEqualsObject() {
+        HasKeys<Integer> otherRequest = createMock(HasKeys.class);
+        ReadOnlyEnumerationMap<Integer> otherMap = createMockBuilder(ReadOnlyEnumerationMap.class).withConstructor(otherRequest).createMock();
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> otherKeys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(otherRequest.getKeys()).andReturn(otherKeys);
+
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("first");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("second");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValue("first")).andReturn(1);
+        expect(extractor.getValue("second")).andReturn(2);
+
+        expect(otherKeys.hasMoreElements()).andReturn(true);
+        expect(otherKeys.nextElement()).andReturn("first");
+        expect(otherKeys.hasMoreElements()).andReturn(true);
+        expect(otherKeys.nextElement()).andReturn("second");
+        expect(otherKeys.hasMoreElements()).andReturn(false);
+
+        expect(otherRequest.getValue("first")).andReturn(1);
+        expect(otherRequest.getValue("second")).andReturn(2);
+
+        replay(extractor, otherRequest, otherMap, keys, otherKeys);
+        assertEquals(map, otherMap);
+        verify(extractor, otherRequest, otherMap, keys, otherKeys);
+    }
+
+    @Test
+    public void testEqualsObjectFalse() {
+        HasKeys<Integer> otherRequest = createMock(HasKeys.class);
+        ReadOnlyEnumerationMap<Integer> otherMap = createMockBuilder(ReadOnlyEnumerationMap.class).withConstructor(otherRequest).createMock();
+        Enumeration<String> keys = createMock(Enumeration.class);
+        Enumeration<String> otherKeys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(otherRequest.getKeys()).andReturn(otherKeys);
+
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("first");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("second");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValue("first")).andReturn(1);
+        expect(extractor.getValue("second")).andReturn(2);
+
+        expect(otherKeys.hasMoreElements()).andReturn(true);
+        expect(otherKeys.nextElement()).andReturn("first");
+        expect(otherKeys.hasMoreElements()).andReturn(true);
+        expect(otherKeys.nextElement()).andReturn("second");
+        expect(otherKeys.hasMoreElements()).andReturn(false);
+
+        expect(otherRequest.getValue("first")).andReturn(1);
+        expect(otherRequest.getValue("second")).andReturn(3);
+
+        replay(extractor, otherRequest, otherMap, keys, otherKeys);
+        assertNotEquals(map, otherMap);
+        verify(extractor, otherRequest, otherMap, keys, otherKeys);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ReadOnlyEnumerationMapValuesCollectionTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ReadOnlyEnumerationMapValuesCollectionTest.java
new file mode 100644
index 000000000..4a9862f82
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ReadOnlyEnumerationMapValuesCollectionTest.java
@@ -0,0 +1,310 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.apache.tiles.request.attribute.HasKeys;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link ReadOnlyEnumerationMap#values()}.
+ */
+public class ReadOnlyEnumerationMapValuesCollectionTest {
+    /**
+     * The extractor to use.
+     */
+    private HasKeys<Integer> extractor;
+
+    /**
+     * The collection to test.
+     */
+    private Collection<Integer> coll;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        extractor = createMock(HasKeys.class);
+        ReadOnlyEnumerationMap<Integer> map = new ReadOnlyEnumerationMap<>(extractor);
+        coll = map.values();
+    }
+
+    /**
+     * Tests {@link Collection#add(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAdd() {
+        coll.add(null);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testAddAll() {
+        coll.addAll(null);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testClear() {
+        coll.clear();
+    }
+
+    /**
+     * Tests {@link Collection#contains(Object)}.
+     */
+    @Test
+    public void testContainsValue() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(2);
+
+        replay(extractor, keys);
+        assertTrue(coll.contains(2));
+        verify(extractor, keys);
+    }
+
+    /**
+     * Tests {@link Collection#contains(Object)}.
+     */
+    @Test
+    public void testContainsValueFalse() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(2);
+
+        replay(extractor, keys);
+        assertFalse(coll.contains(3));
+        verify(extractor, keys);
+    }
+
+    @Test
+    public void testContainsAll() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(2);
+
+        replay(extractor, keys);
+        List<Integer> coll = new ArrayList<>();
+        coll.add(1);
+        coll.add(2);
+        assertTrue(this.coll.containsAll(coll));
+        verify(extractor, keys);
+    }
+
+    @Test
+    public void testContainsAllFalse() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(2);
+
+        replay(extractor, keys);
+        List<Integer> coll = new ArrayList<>();
+        coll.add(3);
+        assertFalse(this.coll.containsAll(coll));
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Collection#isEmpty()}.
+     */
+    @Test
+    public void testIsEmpty() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+
+        replay(extractor, keys);
+        assertFalse(coll.isEmpty());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Collection#iterator()}.
+     */
+    @Test
+    public void testIterator() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+
+        expect(extractor.getValue("two")).andReturn(2);
+
+        replay(extractor, keys);
+        Iterator<Integer> entryIt = coll.iterator();
+        assertTrue(entryIt.hasNext());
+        assertEquals(new Integer(2), entryIt.next());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Collection#iterator()}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testIteratorRemove() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+
+        try {
+            replay(extractor, keys);
+            coll.iterator().remove();
+        } finally {
+            verify(extractor, keys);
+        }
+    }
+
+    /**
+     * Tests {@link Collection#remove(Object)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemove() {
+        coll.remove(null);
+    }
+
+    /**
+     * Tests {@link Collection#removeAll(Collection)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRemoveAll() {
+        coll.removeAll(null);
+    }
+
+    /**
+     * Tests {@link Collection#retainAll(Collection)}.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testRetainAll() {
+        coll.retainAll(null);
+    }
+
+    /**
+     * Test method for {@link HeaderValuesMap#size()}.
+     */
+    @Test
+    public void testSize() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        replay(extractor, keys);
+        assertEquals(2, coll.size());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Collection#toArray()}.
+     */
+    @Test
+    public void testToArray() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(2);
+
+        Integer[] entryArray = new Integer[] {1, 2};
+
+        replay(extractor, keys);
+        assertArrayEquals(entryArray, coll.toArray());
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link Collection#toArray(Object[])}.
+     */
+    @Test
+    public void testToArrayTArray() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(2);
+
+        Integer[] entryArray = new Integer[] {1, 2};
+
+        replay(extractor, keys);
+        Integer[] realArray = new Integer[2];
+        assertArrayEquals(entryArray, coll.toArray(realArray));
+        verify(extractor, keys);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/RemovableKeySetTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/RemovableKeySetTest.java
new file mode 100644
index 000000000..e60b02ed6
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/RemovableKeySetTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.apache.tiles.request.attribute.HasRemovableKeys;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link RemovableKeySet}.
+ */
+public class RemovableKeySetTest {
+
+    /**
+     * The extractor to use.
+     */
+    private HasRemovableKeys<Integer> extractor;
+
+    /**
+     * The key set to test.
+     */
+    private RemovableKeySet entrySet;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        extractor = createMock(HasRemovableKeys.class);
+        entrySet = new RemovableKeySet(extractor);
+    }
+
+    /**
+     * Test method for {@link RemovableKeySet#remove(Object)}.
+     */
+    @Test
+    public void testRemove() {
+        expect(extractor.getValue("one")).andReturn(1);
+        extractor.removeValue("one");
+
+        replay(extractor);
+        assertTrue(entrySet.remove("one"));
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link RemovableKeySet#remove(Object)}.
+     */
+    @Test
+    public void testRemoveNoEffect() {
+        expect(extractor.getValue("one")).andReturn(null);
+
+        replay(extractor);
+        assertFalse(entrySet.remove("one"));
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link RemovableKeySet#removeAll(java.util.Collection)}.
+     */
+    @Test
+    public void testRemoveAll() {
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(2);
+        extractor.removeValue("one");
+        extractor.removeValue("two");
+
+        replay(extractor);
+        List<String> coll = new ArrayList<>();
+        coll.add("one");
+        coll.add("two");
+        assertTrue(entrySet.removeAll(coll));
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link RemovableKeySet#retainAll(java.util.Collection)}.
+     */
+    @Test
+    public void testRetainAll() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("three");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        extractor.removeValue("three");
+
+        replay(extractor, keys);
+        List<String> coll = new ArrayList<>();
+        coll.add("one");
+        coll.add("two");
+        assertTrue(entrySet.retainAll(coll));
+        verify(extractor, keys);
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ScopeMapEntrySetTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ScopeMapEntrySetTest.java
new file mode 100644
index 000000000..c8ceea437
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ScopeMapEntrySetTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.apache.tiles.request.attribute.AttributeExtractor;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link ScopeMap#entrySet()}.
+ */
+public class ScopeMapEntrySetTest {
+
+    /**
+     * The extractor to use.
+     */
+    private AttributeExtractor extractor;
+
+    /**
+     * The entry set to test.
+     */
+    private Set<Map.Entry<String, Object>> entrySet;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        extractor = createMock(AttributeExtractor.class);
+        ScopeMap map = new ScopeMap(extractor);
+        entrySet = map.entrySet();
+    }
+
+    /**
+     * Tests {@link Set#add(Object)}.
+     */
+    @Test
+    public void testAdd() {
+        Map.Entry<String, Object> entry = createMock(Map.Entry.class);
+
+        expect(entry.getKey()).andReturn("one");
+        expect(entry.getValue()).andReturn(1);
+        expect(extractor.getValue("one")).andReturn(null);
+
+        extractor.setValue("one", 1);
+
+        replay(extractor, entry);
+        assertTrue(entrySet.add(entry));
+        verify(extractor, entry);
+    }
+
+    /**
+     * Tests {@link Set#add(Object)}.
+     */
+    @Test
+    public void testAddNoEffect() {
+        Map.Entry<String, Object> entry = createMock(Map.Entry.class);
+
+        expect(entry.getKey()).andReturn("one");
+        expect(entry.getValue()).andReturn(1);
+        expect(extractor.getValue("one")).andReturn(1);
+
+        replay(extractor, entry);
+        assertFalse(entrySet.add(entry));
+        verify(extractor, entry);
+    }
+
+    /**
+     * Tests {@link Set#addAll(java.util.Collection)}.
+     */
+    @Test
+    public void testAddAll() {
+        Map.Entry<String, Object> entry1 = createMock(Map.Entry.class);
+        Map.Entry<String, Object> entry2 = createMock(Map.Entry.class);
+
+        expect(entry1.getKey()).andReturn("one");
+        expect(entry1.getValue()).andReturn(1);
+        expect(entry2.getKey()).andReturn("two");
+        expect(entry2.getValue()).andReturn(2);
+        expect(extractor.getValue("one")).andReturn(null);
+        expect(extractor.getValue("two")).andReturn(null);
+
+        extractor.setValue("one", 1);
+        extractor.setValue("two", 2);
+
+        replay(extractor, entry1, entry2);
+        List<Map.Entry<String, Object>> coll = new ArrayList<>();
+        coll.add(entry1);
+        coll.add(entry2);
+        assertTrue(entrySet.addAll(coll));
+        verify(extractor, entry1, entry2);
+    }
+
+    /**
+     * Test method for {@link ScopeMap#clear()}.
+     */
+    @Test
+    public void testClear() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        extractor.removeValue("one");
+        extractor.removeValue("two");
+
+        replay(extractor, keys);
+        entrySet.clear();
+        verify(extractor, keys);
+    }
+
+    /**
+     * Tests {@link Set#remove(Object)}.
+     */
+    @Test
+    public void testRemove() {
+        Map.Entry<String, Object> entry = createMock(Map.Entry.class);
+
+        expect(entry.getKey()).andReturn("one");
+        expect(entry.getValue()).andReturn(1);
+        expect(extractor.getValue("one")).andReturn(1);
+        extractor.removeValue("one");
+
+        replay(extractor, entry);
+        assertTrue(entrySet.remove(entry));
+        verify(extractor, entry);
+    }
+
+    /**
+     * Tests {@link Set#remove(Object)}.
+     */
+    @Test
+    public void testRemoveNoEffect() {
+        Map.Entry<String, Object> entry = createMock(Map.Entry.class);
+
+        expect(entry.getKey()).andReturn("one");
+        expect(extractor.getValue("one")).andReturn(null);
+
+        replay(extractor, entry);
+        assertFalse(entrySet.remove(entry));
+        verify(extractor, entry);
+    }
+
+    /**
+     * Tests {@link Set#addAll(java.util.Collection)}.
+     */
+    @Test
+    public void testRemoveAll() {
+        Map.Entry<String, Object> entry1 = createMock(Map.Entry.class);
+        Map.Entry<String, Object> entry2 = createMock(Map.Entry.class);
+
+        expect(entry1.getKey()).andReturn("one");
+        expect(entry1.getValue()).andReturn(1);
+        expect(entry2.getKey()).andReturn("two");
+        expect(entry2.getValue()).andReturn(2);
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(2);
+        extractor.removeValue("one");
+        extractor.removeValue("two");
+
+        replay(extractor, entry1, entry2);
+        List<Map.Entry<String, Object>> coll = new ArrayList<>();
+        coll.add(entry1);
+        coll.add(entry2);
+        assertTrue(entrySet.removeAll(coll));
+        verify(extractor, entry1, entry2);
+    }
+
+    /**
+     * Tests {@link Set#addAll(java.util.Collection)}.
+     */
+    @Test
+    public void testRetainAll() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("three");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        Map.Entry<String, Object> entry1 = new MapEntry<>("one", 1, false);
+        Map.Entry<String, Object> entry2 = new MapEntry<>("two", 2, false);
+
+        expect(extractor.getValue("one")).andReturn(1);
+        expect(extractor.getValue("two")).andReturn(3);
+        expect(extractor.getValue("three")).andReturn(4);
+        extractor.removeValue("two");
+        extractor.removeValue("three");
+
+        replay(extractor, keys);
+        List<Map.Entry<String, Object>> coll = new ArrayList<>();
+        coll.add(entry1);
+        coll.add(entry2);
+        assertTrue(entrySet.retainAll(coll));
+        verify(extractor, keys);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ScopeMapTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ScopeMapTest.java
new file mode 100644
index 000000000..09853bcae
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/collection/ScopeMapTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.tiles.request.collection;
+
+import org.apache.tiles.request.attribute.AttributeExtractor;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link ScopeMap}.
+ */
+public class ScopeMapTest {
+
+    /**
+     * The map tot est.
+     */
+    private ScopeMap map;
+
+    /**
+     * The extractor to use.
+     */
+    private AttributeExtractor extractor;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        extractor = createMock(AttributeExtractor.class);
+        map = new ScopeMap(extractor);
+    }
+
+    /**
+     * Test method for {@link ScopeMap#clear()}.
+     */
+    @Test
+    public void testClear() {
+        Enumeration<String> keys = createMock(Enumeration.class);
+
+        expect(extractor.getKeys()).andReturn(keys);
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("one");
+        expect(keys.hasMoreElements()).andReturn(true);
+        expect(keys.nextElement()).andReturn("two");
+        expect(keys.hasMoreElements()).andReturn(false);
+
+        extractor.removeValue("one");
+        extractor.removeValue("two");
+
+        replay(extractor, keys);
+        map.clear();
+        verify(extractor, keys);
+    }
+
+    /**
+     * Test method for {@link ScopeMap#keySet()}.
+     */
+    @Test
+    public void testKeySet() {
+        replay(extractor);
+        assertTrue(map.keySet() instanceof RemovableKeySet);
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link ScopeMap#put(String, Object)}.
+     */
+    @Test
+    public void testPutStringObject() {
+        expect(extractor.getValue("one")).andReturn(null);
+        extractor.setValue("one", 1);
+
+        replay(extractor);
+        assertNull(map.put("one", 1));
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link ScopeMap#putAll(Map)}.
+     */
+    @Test
+    public void testPutAllMapOfQextendsStringQextendsObject() {
+        Map<String, Object> items = new LinkedHashMap<>();
+        items.put("one", 1);
+        items.put("two", 2);
+
+        extractor.setValue("one", 1);
+        extractor.setValue("two", 2);
+
+        replay(extractor);
+        map.putAll(items);
+        verify(extractor);
+    }
+
+    /**
+     * Test method for {@link ScopeMap#remove(Object)}.
+     */
+    @Test
+    public void testRemoveObject() {
+        expect(extractor.getValue("one")).andReturn(1);
+        extractor.removeValue("one");
+
+        replay(extractor);
+        assertEquals(1, map.remove("one"));
+        verify(extractor);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/locale/LocaleUtilTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/locale/LocaleUtilTest.java
new file mode 100644
index 000000000..202a68c48
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/locale/LocaleUtilTest.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.tiles.request.locale;
+
+import junit.framework.TestCase;
+
+import java.util.Locale;
+
+/**
+ * Tests {@link LocaleUtil}.
+ */
+public class LocaleUtilTest extends TestCase {
+
+    /**
+     * Test method for {@link LocaleUtil#getParentLocale(Locale)}.
+     */
+    public void testGetParentLocale() {
+        assertNull("The parent locale of NULL_LOCALE is not correct", LocaleUtil.getParentLocale(Locale.ROOT));
+        assertEquals("The parent locale of 'en' is not correct", Locale.ROOT, LocaleUtil.getParentLocale(Locale.ENGLISH));
+        assertEquals("The parent locale of 'en_US' is not correct", Locale.ENGLISH, LocaleUtil.getParentLocale(Locale.US));
+        Locale locale = new Locale("es", "ES", "Traditional_WIN");
+        Locale parentLocale = new Locale("es", "ES");
+        assertEquals("The parent locale of 'es_ES_Traditional_WIN' is not correct", parentLocale, LocaleUtil.getParentLocale(locale));
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/locale/PostfixedApplicationResourceTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/locale/PostfixedApplicationResourceTest.java
new file mode 100644
index 000000000..48bd0a21b
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/locale/PostfixedApplicationResourceTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.tiles.request.locale;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests PostfixedApplicationResource.
+ */
+public class PostfixedApplicationResourceTest {
+
+    private static class TestApplicationResource extends PostfixedApplicationResource {
+        public TestApplicationResource(String path, Locale locale) {
+            super(path, locale);
+        }
+
+        public TestApplicationResource(String localePath) {
+            super(localePath);
+        }
+
+        @Override
+        public InputStream getInputStream() throws IOException {
+            return null;
+        }
+
+        @Override
+        public long getLastModified() throws IOException {
+            return 0;
+        }
+
+    }
+
+    /**
+     * Test getLocalePath(String path, Locale locale).
+     */
+    @Test
+    public void testGetLocalePath() {
+        TestApplicationResource resource = new TestApplicationResource("/my test/path_fr.html");
+        assertEquals("/my test/path.html", resource.getLocalePath(null));
+        assertEquals("/my test/path.html", resource.getLocalePath(Locale.ROOT));
+        assertEquals("/my test/path_it.html", resource.getLocalePath(Locale.ITALIAN));
+        assertEquals("/my test/path_it_IT.html", resource.getLocalePath(Locale.ITALY));
+        assertEquals("/my test/path_en_GB_scotland.html", resource.getLocalePath(new Locale("en", "GB", "scotland")));
+    }
+
+    @Test
+    public void testBuildFromString() {
+        TestApplicationResource resource = new TestApplicationResource("/my test/path_en_GB_scotland.html");
+        assertEquals("/my test/path_en_GB_scotland.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(new Locale("en", "GB", "scotland"), resource.getLocale());
+        resource = new TestApplicationResource("/my test/path_it_IT.html");
+        assertEquals("/my test/path_it_IT.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(Locale.ITALY, resource.getLocale());
+        resource = new TestApplicationResource("/my test/path_it.html");
+        assertEquals("/my test/path_it.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(Locale.ITALIAN, resource.getLocale());
+        resource = new TestApplicationResource("/my test/path.html");
+        assertEquals("/my test/path.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(Locale.ROOT, resource.getLocale());
+        resource = new TestApplicationResource("/my test/path_zz.html");
+        assertEquals("/my test/path_zz.html", resource.getLocalePath());
+        assertEquals("/my test/path_zz.html", resource.getPath());
+        assertEquals(Locale.ROOT, resource.getLocale());
+        resource = new TestApplicationResource("/my test/path_en_ZZ.html");
+        assertEquals("/my test/path_en.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(new Locale("en"), resource.getLocale());
+        resource = new TestApplicationResource("/my test/path_tiles.html");
+        assertEquals("/my test/path_tiles.html", resource.getLocalePath());
+        assertEquals("/my test/path_tiles.html", resource.getPath());
+        assertEquals(Locale.ROOT, resource.getLocale());
+        resource = new TestApplicationResource("/my test/path_longwordthatbreaksISO639.html");
+        assertEquals("/my test/path_longwordthatbreaksISO639.html", resource.getLocalePath());
+        assertEquals("/my test/path_longwordthatbreaksISO639.html", resource.getPath());
+        assertEquals(Locale.ROOT, resource.getLocale());
+        resource = new TestApplicationResource("/my test/path_en_tiles.html");
+        assertEquals("/my test/path_en.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(new Locale("en"), resource.getLocale());
+        resource = new TestApplicationResource("/my test/path_en_longwordthatbreaksISO3166.html");
+        assertEquals("/my test/path_en.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(new Locale("en"), resource.getLocale());
+    }
+
+    @Test
+    public void testBuildFromStringAndLocale() {
+        TestApplicationResource resource = new TestApplicationResource("/my test/path.html", new Locale("en", "GB", "scotland"));
+        assertEquals("/my test/path_en_GB_scotland.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(new Locale("en", "GB", "scotland"), resource.getLocale());
+        resource = new TestApplicationResource("/my test/path.html", Locale.ITALY);
+        assertEquals("/my test/path_it_IT.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(Locale.ITALY, resource.getLocale());
+        resource = new TestApplicationResource("/my test/path.html", Locale.ITALIAN);
+        assertEquals("/my test/path_it.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(Locale.ITALIAN, resource.getLocale());
+        resource = new TestApplicationResource("/my test/path.html", Locale.ROOT);
+        assertEquals("/my test/path.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(Locale.ROOT, resource.getLocale());
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/locale/URLApplicationResourceTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/locale/URLApplicationResourceTest.java
new file mode 100644
index 000000000..964ca1b73
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/locale/URLApplicationResourceTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.tiles.request.locale;
+
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+import java.util.Locale;
+
+import static java.lang.System.setProperty;
+import static java.lang.reflect.Modifier.FINAL;
+import static org.apache.tiles.request.locale.URLApplicationResource.REMOTE_PROTOCOLS_PROPERTY;
+import static org.apache.tiles.request.locale.URLApplicationResource.initRemoteProtocols;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests URLApplicationResource.
+ */
+public class URLApplicationResourceTest {
+
+    private static final String EXPECTED_MESSAGE = "aMessage";
+
+    private static class TestUrlStreamHandler extends URLStreamHandler {
+
+        @Override
+        protected URLConnection openConnection(final URL u) throws IOException {
+            throw new IOException(EXPECTED_MESSAGE);
+        }
+    }
+
+    private static class TestURLStreamHandlerFactory implements URLStreamHandlerFactory {
+
+        @Override
+        public URLStreamHandler createURLStreamHandler(final String protocol) {
+            if ("test1".equals(protocol) || "test2".equals(protocol)) {
+                return new TestUrlStreamHandler();
+            }
+            return null;
+        }
+    }
+
+    @BeforeClass
+    public static void setURLStreamHandlerFactory() {
+        URL.setURLStreamHandlerFactory(new TestURLStreamHandlerFactory());
+    }
+
+    @After
+    public void tearDown() {
+        setProperty(REMOTE_PROTOCOLS_PROPERTY, "");
+    }
+
+    /**
+     * Test getLocalePath(String path, Locale locale).
+     */
+    @Test
+    public void testGetLocalePath() throws MalformedURLException {
+        URLApplicationResource resource = new URLApplicationResource("/my test/path_fr.html", new URL("file:///"));
+        assertEquals("/my test/path.html", resource.getLocalePath(null));
+        assertEquals("/my test/path.html", resource.getLocalePath(Locale.ROOT));
+        assertEquals("/my test/path_it.html", resource.getLocalePath(Locale.ITALIAN));
+        assertEquals("/my test/path_it_IT.html", resource.getLocalePath(Locale.ITALY));
+        assertEquals("/my test/path_en_GB_scotland.html", resource.getLocalePath(new Locale("en", "GB", "scotland")));
+    }
+
+    @Test
+    public void testBuildFromString() throws MalformedURLException {
+        URLApplicationResource resource = new URLApplicationResource("/my test/path_en_GB_scotland.html", new URL("file:///"));
+        assertEquals("/my test/path_en_GB_scotland.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(new Locale("en", "GB", "scotland"), resource.getLocale());
+        resource = new URLApplicationResource("/my test/path_it_IT.html", new URL("file:///"));
+        assertEquals("/my test/path_it_IT.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(Locale.ITALY, resource.getLocale());
+        resource = new URLApplicationResource("/my test/path_it.html", new URL("file:///"));
+        assertEquals("/my test/path_it.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(Locale.ITALIAN, resource.getLocale());
+        resource = new URLApplicationResource("/my test/path.html", new URL("file:///"));
+        assertEquals("/my test/path.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(Locale.ROOT, resource.getLocale());
+        resource = new URLApplicationResource("/my test/path_zz.html", new URL("file:///"));
+        assertEquals("/my test/path_zz.html", resource.getLocalePath());
+        assertEquals("/my test/path_zz.html", resource.getPath());
+        assertEquals(Locale.ROOT, resource.getLocale());
+        resource = new URLApplicationResource("/my test/path_en_ZZ.html", new URL("file:///"));
+        assertEquals("/my test/path_en.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(new Locale("en"), resource.getLocale());
+        resource = new URLApplicationResource("/my test/path_tiles.html", new URL("file:///"));
+        assertEquals("/my test/path_tiles.html", resource.getLocalePath());
+        assertEquals("/my test/path_tiles.html", resource.getPath());
+        assertEquals(Locale.ROOT, resource.getLocale());
+        resource = new URLApplicationResource("/my test/path_longwordthatbreaksISO639.html", new URL("file:///"));
+        assertEquals("/my test/path_longwordthatbreaksISO639.html", resource.getLocalePath());
+        assertEquals("/my test/path_longwordthatbreaksISO639.html", resource.getPath());
+        assertEquals(Locale.ROOT, resource.getLocale());
+        resource = new URLApplicationResource("/my test/path_en_tiles.html", new URL("file:///"));
+        assertEquals("/my test/path_en.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(new Locale("en"), resource.getLocale());
+        resource = new URLApplicationResource("/my test/path_en_longwordthatbreaksISO3166.html", new URL("file:///"));
+        assertEquals("/my test/path_en.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(new Locale("en"), resource.getLocale());
+    }
+
+    @Test
+    public void testBuildFromStringAndLocale() throws MalformedURLException {
+        URLApplicationResource resource = new URLApplicationResource("/my test/path.html", new Locale("en", "GB", "scotland"), new URL("file:///"));
+        assertEquals("/my test/path_en_GB_scotland.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(new Locale("en", "GB", "scotland"), resource.getLocale());
+        resource = new URLApplicationResource("/my test/path.html", Locale.ITALY, new URL("file:///"));
+        assertEquals("/my test/path_it_IT.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(Locale.ITALY, resource.getLocale());
+        resource = new URLApplicationResource("/my test/path.html", Locale.ITALIAN, new URL("file:///"));
+        assertEquals("/my test/path_it.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(Locale.ITALIAN, resource.getLocale());
+        resource = new URLApplicationResource("/my test/path.html", Locale.ROOT, new URL("file:///"));
+        assertEquals("/my test/path.html", resource.getLocalePath());
+        assertEquals("/my test/path.html", resource.getPath());
+        assertEquals(Locale.ROOT, resource.getLocale());
+    }
+
+    @Test
+    public void testGetLastModified() throws IOException {
+        URL url = getClass().getClassLoader().getResource("org/apache/tiles/request/locale/resource.txt");
+        assertNotNull(url);
+        URLApplicationResource resource = new URLApplicationResource("org/apache/tiles/request/locale/resource.txt", url);
+        assertTrue(resource.getLastModified() > 0);
+    }
+
+    @Test
+    public void testGetLastModifiedWithSpace() throws IOException {
+        URL url = getClass().getClassLoader().getResource("org/apache/tiles/request/locale/resource with space.txt");
+        assertNotNull(url);
+        URLApplicationResource resource = new URLApplicationResource("org/apache/tiles/request/locale/resource with space.txt", url);
+        assertTrue(resource.getLastModified() > 0);
+    }
+
+    @Test
+    public void testGetInputStream() throws IOException {
+        URL url = getClass().getClassLoader().getResource("org/apache/tiles/request/locale/resource.txt");
+        assertNotNull(url);
+        URLApplicationResource resource = new URLApplicationResource("org/apache/tiles/request/locale/resource.txt", url);
+        InputStream is = resource.getInputStream();
+        assertNotNull(is);
+        is.close();
+    }
+
+    @Test
+    public void testGetInputStreamWithSpace() throws IOException {
+        URL url = getClass().getClassLoader().getResource("org/apache/tiles/request/locale/resource with space.txt");
+        assertNotNull(url);
+        URLApplicationResource resource = new URLApplicationResource("org/apache/tiles/request/locale/resource with space.txt", url);
+        InputStream is = resource.getInputStream();
+        assertNotNull(is);
+        is.close();
+    }
+
+    @Test(expected = FileNotFoundException.class)
+    public void testLocalProtocol() throws IOException {
+        URL url = new URL("file://foo/bar.txt");
+        URLApplicationResource resource = new URLApplicationResource("org/apache/tiles/request/locale/resource.txt", url);
+        resource.getInputStream();
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/reflect/CannotInstantiateObjectExceptionTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/reflect/CannotInstantiateObjectExceptionTest.java
new file mode 100644
index 000000000..b170ca4e7
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/reflect/CannotInstantiateObjectExceptionTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.tiles.request.reflect;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests {@link CannotInstantiateObjectException}.
+ */
+public class CannotInstantiateObjectExceptionTest {
+
+    /**
+     * Test method for {@link CannotInstantiateObjectException#CannotInstantiateObjectException(String, Throwable)}.
+     */
+    @Test
+    public void testCannotInstantiateObjectExceptionStringThrowable() {
+        Throwable cause = new Throwable();
+        CannotInstantiateObjectException exception = new CannotInstantiateObjectException("my message", cause);
+        assertEquals("my message", exception.getMessage());
+        assertEquals(cause, exception.getCause());
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/reflect/ClassUtilTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/reflect/ClassUtilTest.java
new file mode 100644
index 000000000..db01a2414
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/reflect/ClassUtilTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.tiles.request.reflect;
+
+import org.junit.Test;
+
+import java.beans.PropertyDescriptor;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Tests {@link ClassUtil}.
+ */
+public class ClassUtilTest {
+
+    /**
+     * The size of descriptor map.
+     */
+    private static final int MAP_SIZE = 3;
+
+    /**
+     * Test method for {@link ClassUtil#collectBeanInfo(Class, Map)}.
+     */
+    @Test
+    public void testCollectBeanInfo() {
+        Map<String, PropertyDescriptor> name2descriptor = new HashMap<>();
+        ClassUtil.collectBeanInfo(TestInterface.class, name2descriptor);
+        assertEquals(MAP_SIZE, name2descriptor.size());
+        PropertyDescriptor descriptor = name2descriptor.get("value");
+        assertEquals("value", descriptor.getName());
+        assertEquals(int.class, descriptor.getPropertyType());
+        assertNotNull(descriptor.getReadMethod());
+        assertNotNull(descriptor.getWriteMethod());
+        descriptor = name2descriptor.get("value2");
+        assertEquals("value2", descriptor.getName());
+        assertEquals(long.class, descriptor.getPropertyType());
+        assertNotNull(descriptor.getReadMethod());
+        assertNull(descriptor.getWriteMethod());
+        descriptor = name2descriptor.get("value3");
+        assertEquals("value3", descriptor.getName());
+        assertEquals(String.class, descriptor.getPropertyType());
+        assertNull(descriptor.getReadMethod());
+        assertNotNull(descriptor.getWriteMethod());
+    }
+
+    /**
+     * Test method for {@link ClassUtil#getClass(String, Class)}.
+     * @throws ClassNotFoundException If something goes wrong.
+     */
+    @Test
+    public void testGetClass() throws ClassNotFoundException {
+        assertEquals(TestInterface.class, ClassUtil.getClass(
+                TestInterface.class.getName(), Object.class));
+    }
+
+    /**
+     * Test method for {@link ClassUtil#getClass(String, Class)}.
+     * @throws ClassNotFoundException If something goes wrong.
+     */
+    @Test(expected = ClassNotFoundException.class)
+    public void testGetClassException() throws ClassNotFoundException {
+        ClassUtil.getClass("this.class.does.not.Exist", Object.class);
+    }
+
+    /**
+     * Test method for {@link ClassUtil#instantiate(String, boolean)}.
+     */
+    @Test
+    public void testInstantiate() {
+        assertNotNull(ClassUtil.instantiate(TestClass.class.getName(), true));
+        assertNull(ClassUtil.instantiate("this.class.does.not.Exist", true));
+    }
+
+    /**
+     * Test method for {@link ClassUtil#instantiate(String, boolean)}.
+     */
+    @Test
+    public void testInstantiateOneParameter() {
+        assertNotNull(ClassUtil.instantiate(TestClass.class.getName()));
+    }
+
+    /**
+     * Test method for {@link ClassUtil#instantiate(String)}.
+     */
+    @Test(expected = CannotInstantiateObjectException.class)
+    public void testInstantiateOneParameterException() {
+        assertNotNull(ClassUtil.instantiate("this.class.does.not.Exist"));
+    }
+
+    /**
+     * Test method for {@link ClassUtil#instantiate(String)}.
+     */
+    @Test(expected = CannotInstantiateObjectException.class)
+    public void testInstantiateInstantiationException() {
+        ClassUtil.instantiate(TestInterface.class.getName());
+    }
+
+    /**
+     * Test method for {@link ClassUtil#instantiate(String)}.
+     */
+    @Test(expected = CannotInstantiateObjectException.class)
+    public void testInstantiateIllegalAccessException() {
+        ClassUtil.instantiate(TestPrivateClass.class.getName());
+    }
+
+    /**
+     * Interface to be used as test.
+     *
+     * @version $Rev$ $Date$
+     */
+    public interface TestInterface {
+
+        /**
+         * The value.
+         *
+         * @return The value.
+         */
+        int getValue();
+
+        /**
+         * The value.
+         *
+         * @param value The value.
+         */
+        void setValue(int value);
+
+        /**
+         * The value.
+         *
+         * @return The value.
+         */
+        long getValue2();
+
+        /**
+         * The value.
+         *
+         * @param value3 The value.
+         */
+        @SuppressWarnings("unused")
+        void setValue3(String value3);
+    }
+
+    /**
+     * A test static class.
+     */
+    public static class TestClass {
+    }
+
+    /**
+     * A test static private class.
+     */
+    private static class TestPrivateClass {
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/render/BasicRendererFactoryTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/render/BasicRendererFactoryTest.java
new file mode 100644
index 000000000..c133b4cb2
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/render/BasicRendererFactoryTest.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.tiles.request.render;
+
+import org.apache.tiles.request.ApplicationContext;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertSame;
+
+/**
+ * Basic renderer factory implementation.
+ */
+public class BasicRendererFactoryTest {
+
+    /**
+     * The renderer factory.
+     */
+    private BasicRendererFactory rendererFactory;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        rendererFactory = new BasicRendererFactory();
+        ApplicationContext applicationContext = createMock(ApplicationContext.class);
+        replay(applicationContext);
+    }
+
+    /**
+     * Tests execution and
+     * {@link BasicRendererFactory#getRenderer(String)}.
+     */
+    @Test
+    public void testInitAndGetRenderer() {
+        Renderer renderer1 = createMock(Renderer.class);
+        Renderer renderer2 = createMock(Renderer.class);
+        Renderer renderer3 = createMock(Renderer.class);
+        Renderer renderer4 = createMock(Renderer.class);
+        ApplicationContext applicationContext = createMock(ApplicationContext.class);
+
+        replay(renderer1, renderer2, renderer3, renderer4, applicationContext);
+        rendererFactory.registerRenderer("string", renderer1);
+        rendererFactory.registerRenderer("test", renderer2);
+        rendererFactory.registerRenderer("test2", renderer3);
+        rendererFactory.setDefaultRenderer(renderer4);
+        Renderer renderer = rendererFactory.getRenderer("string");
+        assertSame(renderer1, renderer);
+        renderer = rendererFactory.getRenderer("test");
+        assertSame(renderer2, renderer);
+        renderer = rendererFactory.getRenderer("test2");
+        assertSame(renderer3, renderer);
+        renderer = rendererFactory.getRenderer(null);
+        assertSame(renderer4, renderer);
+        verify(renderer1, renderer2, renderer3, renderer4, applicationContext);
+    }
+
+    /**
+     * Tests execution and
+     * {@link BasicRendererFactory#getRenderer(String)}.
+     */
+    @Test(expected = NoSuchRendererException.class)
+    public void testGetRendererException() {
+        Renderer renderer1 = createMock(Renderer.class);
+        Renderer renderer2 = createMock(Renderer.class);
+        Renderer renderer3 = createMock(Renderer.class);
+        Renderer renderer4 = createMock(Renderer.class);
+        ApplicationContext applicationContext = createMock(ApplicationContext.class);
+
+        replay(renderer1, renderer2, renderer3, renderer4, applicationContext);
+        rendererFactory.registerRenderer("string", renderer1);
+        rendererFactory.registerRenderer("test", renderer2);
+        rendererFactory.registerRenderer("test2", renderer3);
+        rendererFactory.setDefaultRenderer(renderer4);
+        try {
+            rendererFactory.getRenderer("nothing");
+        } finally {
+            verify(renderer1, renderer2, renderer3, renderer4, applicationContext);
+        }
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/render/ChainedDelegateRendererTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/render/ChainedDelegateRendererTest.java
new file mode 100644
index 000000000..31daf7fe1
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/render/ChainedDelegateRendererTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.tiles.request.render;
+
+import org.apache.tiles.request.Request;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+/**
+ * Tests {@link ChainedDelegateRenderer}.
+ */
+public class ChainedDelegateRendererTest {
+
+    /**
+     * The renderer.
+     */
+    private ChainedDelegateRenderer renderer;
+
+    /**
+     * A mock string attribute renderer.
+     */
+    private Renderer stringRenderer;
+
+    /**
+     * A mock template attribute renderer.
+     */
+    private Renderer templateRenderer;
+
+    /**
+     * A mock definition attribute renderer.
+     */
+    private Renderer definitionRenderer;
+
+    /**
+     * Sets up the test.
+     */
+    @Before
+    public void setUp() {
+        stringRenderer = createMock(Renderer.class);
+        templateRenderer = createMock(Renderer.class);
+        definitionRenderer = createMock(Renderer.class);
+        renderer = new ChainedDelegateRenderer();
+        renderer.addAttributeRenderer(definitionRenderer);
+        renderer.addAttributeRenderer(templateRenderer);
+        renderer.addAttributeRenderer(stringRenderer);
+    }
+
+    /**
+     * Tests
+     * {@link ChainedDelegateRenderer#render(String, Request)}
+     * writing a definition.
+     *
+     * @throws IOException If something goes wrong during rendition.
+     */
+    @Test
+    public void testWriteDefinition() throws IOException {
+        Request requestContext = EasyMock.createMock(Request.class);
+
+        expect(definitionRenderer.isRenderable("my.definition", requestContext)).andReturn(Boolean.TRUE);
+        definitionRenderer.render("my.definition", requestContext);
+
+        replay(requestContext, stringRenderer, templateRenderer, definitionRenderer);
+        renderer.render("my.definition", requestContext);
+        verify(requestContext, stringRenderer, templateRenderer, definitionRenderer);
+    }
+
+    /**
+     * Tests
+     * {@link ChainedDelegateRenderer#render(String, Request)}
+     * writing a definition.
+     *
+     * @throws IOException If something goes wrong during rendition.
+     */
+    @Test(expected = NullPointerException.class)
+    public void testWriteNull() throws IOException {
+        StringWriter writer = new StringWriter();
+        Request requestContext = EasyMock.createMock(Request.class);
+
+        replay(requestContext, stringRenderer, templateRenderer, definitionRenderer);
+        try {
+            renderer.render(null, requestContext);
+        } finally {
+            writer.close();
+            verify(requestContext, stringRenderer, templateRenderer, definitionRenderer);
+        }
+    }
+
+    /**
+     * Tests
+     * {@link ChainedDelegateRenderer#render(String, Request)}
+     * writing a definition.
+     *
+     * @throws IOException If something goes wrong during rendition.
+     */
+    @Test(expected = CannotRenderException.class)
+    public void testWriteNotRenderable() throws IOException {
+        StringWriter writer = new StringWriter();
+        Request requestContext = EasyMock.createMock(Request.class);
+
+        expect(definitionRenderer.isRenderable("Result", requestContext)).andReturn(Boolean.FALSE);
+        expect(templateRenderer.isRenderable("Result", requestContext)).andReturn(Boolean.FALSE);
+        expect(stringRenderer.isRenderable("Result", requestContext)).andReturn(Boolean.FALSE);
+
+        replay(requestContext, stringRenderer, templateRenderer, definitionRenderer);
+        try {
+            renderer.render("Result", requestContext);
+        } finally {
+            writer.close();
+            verify(requestContext, stringRenderer, templateRenderer, definitionRenderer);
+        }
+    }
+
+    /**
+     * Tests
+     * {@link ChainedDelegateRenderer#render(String, Request)}
+     * writing a string.
+     *
+     * @throws IOException If something goes wrong during rendition.
+     */
+    @Test
+    public void testWriteString() throws IOException {
+        Request requestContext = EasyMock.createMock(Request.class);
+        expect(definitionRenderer.isRenderable("Result", requestContext)).andReturn(Boolean.FALSE);
+        expect(templateRenderer.isRenderable("Result", requestContext)).andReturn(Boolean.FALSE);
+        expect(stringRenderer.isRenderable("Result", requestContext)).andReturn(Boolean.TRUE);
+        stringRenderer.render("Result", requestContext);
+
+        replay(requestContext, stringRenderer, templateRenderer, definitionRenderer);
+        renderer.render("Result", requestContext);
+        verify(requestContext, stringRenderer, templateRenderer, definitionRenderer);
+    }
+
+    /**
+     * Tests
+     * {@link ChainedDelegateRenderer#render(String, Request)}
+     * writing a template.
+     *
+     * @throws IOException If something goes wrong during rendition.
+     */
+    @Test
+    public void testWriteTemplate() throws IOException {
+        StringWriter writer = new StringWriter();
+        Request requestContext = EasyMock.createMock(Request.class);
+        templateRenderer.render("/myTemplate.jsp", requestContext);
+        expect(definitionRenderer.isRenderable("/myTemplate.jsp", requestContext)).andReturn(Boolean.FALSE);
+        expect(templateRenderer.isRenderable("/myTemplate.jsp", requestContext)).andReturn(Boolean.TRUE);
+
+        replay(requestContext, stringRenderer, templateRenderer, definitionRenderer);
+        renderer.render("/myTemplate.jsp", requestContext);
+        writer.close();
+        verify(requestContext, stringRenderer, templateRenderer, definitionRenderer);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/render/DispatchRendererTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/render/DispatchRendererTest.java
new file mode 100644
index 000000000..7f158ce69
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/render/DispatchRendererTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.tiles.request.render;
+
+import org.apache.tiles.request.DispatchRequest;
+import org.apache.tiles.request.Request;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link DispatchRenderer}.
+ */
+public class DispatchRendererTest {
+
+    /**
+     * The renderer.
+     */
+    private DispatchRenderer renderer;
+
+    @Before
+    public void setUp() {
+        renderer = new DispatchRenderer();
+    }
+
+    @Test
+    public void testWrite() throws IOException {
+        DispatchRequest requestContext = createMock(DispatchRequest.class);
+        requestContext.dispatch("/myTemplate.jsp");
+        replay(requestContext);
+        renderer.render("/myTemplate.jsp", requestContext);
+        verify(requestContext);
+    }
+
+    @Test(expected = CannotRenderException.class)
+    public void testWriteNull() throws IOException {
+        DispatchRequest requestContext = createMock(DispatchRequest.class);
+        replay(requestContext);
+        renderer.render(null, requestContext);
+        verify(requestContext);
+    }
+
+    @Test
+    public void testIsRenderable() {
+        Request requestContext = createMock(DispatchRequest.class);
+        replay(requestContext);
+        assertTrue(renderer.isRenderable("/myTemplate.jsp", requestContext));
+        assertFalse(renderer.isRenderable(null, requestContext));
+        verify(requestContext);
+    }
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/render/NoSuchRendererExceptionTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/render/NoSuchRendererExceptionTest.java
new file mode 100644
index 000000000..5c51a730c
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/render/NoSuchRendererExceptionTest.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.tiles.request.render;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Tests {@link NoSuchRendererException}.
+ */
+public class NoSuchRendererExceptionTest {
+
+    /**
+     * Test method for {@link NoSuchRendererException#NoSuchRendererException(String)}.
+     */
+    @Test
+    public void testNoSuchRendererExceptionString() {
+        NoSuchRendererException exception = new NoSuchRendererException("my message");
+        assertEquals("my message", exception.getMessage());
+        assertNull(exception.getCause());
+    }
+
+}
diff --git a/plugins/tiles/src/test/java/org/apache/tiles/request/render/StringRendererTest.java b/plugins/tiles/src/test/java/org/apache/tiles/request/render/StringRendererTest.java
new file mode 100644
index 000000000..cce4710af
--- /dev/null
+++ b/plugins/tiles/src/test/java/org/apache/tiles/request/render/StringRendererTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.tiles.request.render;
+
+import org.apache.tiles.request.Request;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests {@link StringRenderer}.
+ */
+public class StringRendererTest {
+
+    /**
+     * The renderer.
+     */
+    private StringRenderer renderer;
+
+    @Before
+    public void setUp() {
+        renderer = new StringRenderer();
+    }
+
+    /**
+     * Tests
+     * {@link StringRenderer#render(String, Request)}.
+     *
+     * @throws IOException If something goes wrong during rendition.
+     */
+    @Test
+    public void testWrite() throws IOException {
+        StringWriter writer = new StringWriter();
+        Request requestContext = createMock(Request.class);
+        expect(requestContext.getWriter()).andReturn(writer);
+        replay(requestContext);
+        renderer.render("Result", requestContext);
+        writer.close();
+        assertEquals("Not written 'Result'", "Result", writer.toString());
+        verify(requestContext);
+    }
+
+    /**
+     * Tests
+     * {@link StringRenderer#isRenderable(String, Request)}.
+     */
+    @Test
+    public void testIsRenderable() {
+        Request requestContext = createMock(Request.class);
+        replay(requestContext);
+        assertTrue(renderer.isRenderable("Result", requestContext));
+        verify(requestContext);
+    }
+}
diff --git a/plugins/tiles/src/test/resources/org/apache/tiles/request/locale/resource with space.txt b/plugins/tiles/src/test/resources/org/apache/tiles/request/locale/resource with space.txt
new file mode 100644
index 000000000..82cb4afe8
--- /dev/null
+++ b/plugins/tiles/src/test/resources/org/apache/tiles/request/locale/resource with space.txt	
@@ -0,0 +1,17 @@
+ * 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.
+test
diff --git a/plugins/tiles/src/test/resources/org/apache/tiles/request/locale/resource.txt b/plugins/tiles/src/test/resources/org/apache/tiles/request/locale/resource.txt
new file mode 100644
index 000000000..82cb4afe8
--- /dev/null
+++ b/plugins/tiles/src/test/resources/org/apache/tiles/request/locale/resource.txt
@@ -0,0 +1,17 @@
+ * 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.
+test