You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 10:14:01 UTC

[sling-org-apache-sling-servlet-helpers] 01/06: SLING-5428 Move MockSlingHttpServletRequest+Response to org.apache.sling.servlet-helpers

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

rombert pushed a commit to annotated tag org.apache.sling.servlet-helpers-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-servlet-helpers.git

commit 43ed44156fb5a29a1948b2661a9fbffd0bc03ddc
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Wed Jan 13 23:07:09 2016 +0000

    SLING-5428 Move MockSlingHttpServletRequest+Response to org.apache.sling.servlet-helpers
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/servlet-helpers@1724523 13f79535-47bb-0310-9956-ffa450edef68
---
 README.txt                                         |  29 +
 pom.xml                                            | 106 +++
 .../apache/sling/servlethelpers/CookieSupport.java |  53 ++
 .../apache/sling/servlethelpers/HeaderSupport.java | 175 +++++
 .../sling/servlethelpers/MockHttpSession.java      | 169 +++++
 .../MockRequestDispatcherFactory.java              |  51 ++
 .../sling/servlethelpers/MockRequestParameter.java | 105 +++
 .../servlethelpers/MockRequestParameterMap.java    | 101 +++
 .../sling/servlethelpers/MockRequestPathInfo.java  |  89 +++
 .../sling/servlethelpers/MockServletContext.java   | 304 ++++++++
 .../MockSlingHttpServletRequest.java               | 805 +++++++++++++++++++++
 .../MockSlingHttpServletResponse.java              | 282 ++++++++
 .../sling/servlethelpers/ResponseBodySupport.java  | 100 +++
 .../apache/sling/servlethelpers/package-info.java  |  23 +
 .../sling/servlethelpers/MockHttpSessionTest.java  | 103 +++
 .../servlethelpers/MockRequestPathInfoTest.java    |  69 ++
 .../servlethelpers/MockServletContextTest.java     |  43 ++
 .../MockSlingHttpServletRequestTest.java           | 328 +++++++++
 .../MockSlingHttpServletResponseTest.java          | 177 +++++
 19 files changed, 3112 insertions(+)

diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..64e9bb4
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,29 @@
+Apache Sling Servlet Helpers
+
+Mock implementations of SlingHttpServletRequest, SlingHttpServletRepsonse and related classes.
+
+These are used by sling-mock, but can be used standalone and deployed to an OSGi container as well.
+
+
+Getting Started
+===============
+
+This component uses a Maven 2 (http://maven.apache.org/) build
+environment. It requires a Java 5 JDK (or higher) and Maven (http://maven.apache.org/)
+2.0.7 or later. We recommend to use the latest Maven version.
+
+If you have Maven 2 installed, you can compile and
+package the jar using the following command:
+
+    mvn package
+
+See the Maven 2 documentation for other build features.
+
+The latest source code for this component is available in the
+Subversion (http://subversion.tigris.org/) source repository of
+the Apache Software Foundation. If you have Subversion installed,
+you can checkout the latest source using the following command:
+
+    svn checkout http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/servlet-helpers
+
+See the Subversion documentation for other source control features.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..56f63db
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>26</version>
+        <relativePath/>
+    </parent>
+
+    <artifactId>org.apache.sling.servlet-helpers</artifactId>
+    <packaging>bundle</packaging>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <name>Apache Sling Servlet Helpers</name>
+    <description>Mock implementations of SlingHttpServletRequest, SlingHttpServletRepsonse and related classes.</description>
+
+    <scm>
+        <connection>
+            scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/servlet-helpers
+        </connection>
+        <developerConnection>
+            scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/servlet-helpers
+        </developerConnection>
+        <url>
+            http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/servlet-helpers
+        </url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>3.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.4.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+            <version>3.2.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/src/main/java/org/apache/sling/servlethelpers/CookieSupport.java b/src/main/java/org/apache/sling/servlethelpers/CookieSupport.java
new file mode 100644
index 0000000..ffdde3c
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/CookieSupport.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.sling.servlethelpers;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.servlet.http.Cookie;
+
+/**
+ * Manages cookies for request and response.
+ */
+class CookieSupport {
+
+    private Map<String, Cookie> cookies = new LinkedHashMap<String, Cookie>();
+
+    public void addCookie(Cookie cookie) {
+        cookies.put(cookie.getName(), cookie);
+    }
+
+    public Cookie getCookie(String name) {
+        return cookies.get(name);
+    }
+
+    public Cookie[] getCookies() {
+        if (cookies.isEmpty()) {
+            return null;
+        } else {
+            return cookies.values().toArray(new Cookie[cookies.size()]);
+        }
+    }
+
+    public void reset() {
+        cookies.clear();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/HeaderSupport.java b/src/main/java/org/apache/sling/servlethelpers/HeaderSupport.java
new file mode 100644
index 0000000..48a5f39
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/HeaderSupport.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.sling.servlethelpers;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.Vector;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+
+/**
+ * Manage HTTP headers for request and response.
+ */
+class HeaderSupport {
+
+    private static final String RFC_1123_DATE_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z";
+    private static final DateFormat RFC1123_DATE_FORMAT = new SimpleDateFormat(RFC_1123_DATE_PATTERN, Locale.US);
+    private static final TimeZone TIMEZONE_GMT = TimeZone.getTimeZone("GMT");
+    static {
+        RFC1123_DATE_FORMAT.setTimeZone(TIMEZONE_GMT);
+    }
+
+    private List<HeaderValue> headers = new ArrayList<HeaderValue>();
+
+    private static class HeaderValue {
+
+        private String key;
+        private String value;
+
+        public HeaderValue(String key, String value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        public String getKey() {
+            return this.key;
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+
+    }
+
+    public void addHeader(String name, String value) {
+        headers.add(new HeaderValue(name, value));
+    }
+
+    public void addIntHeader(String name, int value) {
+        headers.add(new HeaderValue(name, Integer.toString(value)));
+    }
+
+    public void addDateHeader(String name, long date) {
+        Calendar calendar = Calendar.getInstance(TIMEZONE_GMT, Locale.US);
+        calendar.setTimeInMillis(date);
+        headers.add(new HeaderValue(name, formatDate(calendar)));
+    }
+
+    public void setHeader(String name, String value) {
+        removeHeaders(name);
+        addHeader(name, value);
+    }
+
+    public void setIntHeader(String name, int value) {
+        removeHeaders(name);
+        addIntHeader(name, value);
+    }
+
+    public void setDateHeader(String name, long date) {
+        removeHeaders(name);
+        addDateHeader(name, date);
+    }
+
+    private void removeHeaders(String name) {
+        for (int i = this.headers.size() - 1; i >= 0; i--) {
+            if (StringUtils.equals(this.headers.get(i).getKey(), name)) {
+                headers.remove(i);
+            }
+        }
+    }
+
+    public boolean containsHeader(String name) {
+        return !getHeaders(name).isEmpty();
+    }
+
+    public String getHeader(String name) {
+        Collection<String> values = getHeaders(name);
+        if (!values.isEmpty()) {
+            return values.iterator().next();
+        } else {
+            return null;
+        }
+    }
+
+    public int getIntHeader(String name) {
+        String value = getHeader(name);
+        return NumberUtils.toInt(value);
+    }
+
+    public long getDateHeader(String name) {
+        String value = getHeader(name);
+        if (StringUtils.isEmpty(value)) {
+            return 0L;
+        } else {
+            try {
+                return parseDate(value).getTimeInMillis();
+            } catch (ParseException ex) {
+                return 0L;
+            }
+        }
+    }
+
+    public Collection<String> getHeaders(String name) {
+        List<String> values = new ArrayList<String>();
+        for (HeaderValue entry : headers) {
+            if (StringUtils.equals(entry.getKey(), name)) {
+                values.add(entry.getValue());
+            }
+        }
+        return values;
+    }
+
+    public Collection<String> getHeaderNames() {
+        Set<String> values = new HashSet<String>();
+        for (HeaderValue entry : headers) {
+            values.add(entry.getKey());
+        }
+        return values;
+    }
+
+    public void reset() {
+        headers.clear();
+    }
+
+    public static Enumeration<String> toEnumeration(Collection<String> collection) {
+        return new Vector<String>(collection).elements();
+    }
+
+    private static synchronized String formatDate(Calendar date) {
+        return RFC1123_DATE_FORMAT.format(date.getTime());
+    }
+
+    private static synchronized Calendar parseDate(String dateString) throws ParseException {
+        RFC1123_DATE_FORMAT.parse(dateString);
+        return RFC1123_DATE_FORMAT.getCalendar();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockHttpSession.java b/src/main/java/org/apache/sling/servlethelpers/MockHttpSession.java
new file mode 100644
index 0000000..31d672b
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockHttpSession.java
@@ -0,0 +1,169 @@
+/*
+ * 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.sling.servlethelpers;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.collections.IteratorUtils;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * Mock {@link HttpSession} implementation.
+ */
+@ConsumerType
+public class MockHttpSession implements HttpSession {
+
+    private final ServletContext servletContext;
+    private final Map<String, Object> attributeMap = new HashMap<String, Object>();
+    private final String sessionID = UUID.randomUUID().toString();
+    private final long creationTime = System.currentTimeMillis();
+    private boolean invalidated = false;
+    private boolean isNew = true;
+    private int maxActiveInterval = 1800;
+    
+    public MockHttpSession() {
+        this.servletContext = newMockServletContext();
+    }
+
+    protected MockServletContext newMockServletContext() {
+        return new MockServletContext();
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        return this.servletContext;
+    }
+    
+    @Override
+    public Object getAttribute(final String name) {
+        checkInvalidatedState();
+        return this.attributeMap.get(name);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        checkInvalidatedState();
+        return IteratorUtils.asEnumeration(this.attributeMap.keySet().iterator());
+    }
+
+    @Override
+    public String getId() {
+        return this.sessionID;
+    }
+
+    @Override
+    public long getCreationTime() {
+        checkInvalidatedState();
+        return this.creationTime;
+    }
+
+    @Override
+    public Object getValue(final String name) {
+        checkInvalidatedState();
+        return getAttribute(name);
+    }
+
+    @Override
+    public String[] getValueNames() {
+        checkInvalidatedState();
+        return this.attributeMap.keySet().toArray(new String[this.attributeMap.keySet().size()]);
+    }
+
+    @Override
+    public void putValue(final String name, final Object value) {
+        checkInvalidatedState();
+        setAttribute(name, value);
+    }
+
+    @Override
+    public void removeAttribute(final String name) {
+        checkInvalidatedState();
+        this.attributeMap.remove(name);
+    }
+
+    @Override
+    public void removeValue(final String name) {
+        checkInvalidatedState();
+        this.attributeMap.remove(name);
+    }
+
+    @Override
+    public void setAttribute(final String name, final Object value) {
+        checkInvalidatedState();
+        this.attributeMap.put(name, value);
+    }
+
+    @Override
+    public void invalidate() {
+        checkInvalidatedState();
+        this.invalidated = true;
+    }
+    
+    private void checkInvalidatedState() {
+        if (invalidated) {
+            throw new IllegalStateException("Session is already invalidated.");
+        }
+    }
+    
+    public boolean isInvalidated() {
+        return invalidated;
+    }
+
+    @Override
+    public boolean isNew() {
+        checkInvalidatedState();
+        return isNew;
+    }
+    
+    public void setNew(boolean isNew) {
+        this.isNew = isNew;
+    }
+
+    @Override
+    public long getLastAccessedTime() {
+        checkInvalidatedState();
+        return creationTime;
+    }
+
+    @Override
+    public int getMaxInactiveInterval() {
+        return maxActiveInterval;
+    }
+
+    @Override
+    public void setMaxInactiveInterval(final int interval) {
+        this.maxActiveInterval = interval;
+    }
+
+    // --- unsupported operations ---
+    @Override
+    @SuppressWarnings("deprecation")
+    public javax.servlet.http.HttpSessionContext getSessionContext() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockRequestDispatcherFactory.java b/src/main/java/org/apache/sling/servlethelpers/MockRequestDispatcherFactory.java
new file mode 100644
index 0000000..5664035
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockRequestDispatcherFactory.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.sling.servlethelpers;
+
+import javax.servlet.RequestDispatcher;
+
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.resource.Resource;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * Interface to create a mock {@link RequestDispatcher} when calling the getRequestDispatcher methods
+ * on {@link MockSlingHttpServletRequest} instances.
+ */
+@ConsumerType
+public interface MockRequestDispatcherFactory {
+
+    /**
+     * Get request dispatcher for given path.
+     * @param path Path
+     * @param options Options. Null if no options are provided.
+     * @return Request dispatcher
+     */
+    RequestDispatcher getRequestDispatcher(String path, RequestDispatcherOptions options);
+
+    /**
+     * Get request dispatcher for given resource.
+     * @param resource Resource
+     * @param options Options. Null if no options are provided.
+     * @return Request dispatcher
+     */
+    RequestDispatcher getRequestDispatcher(Resource resource, RequestDispatcherOptions options);
+
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockRequestParameter.java b/src/main/java/org/apache/sling/servlethelpers/MockRequestParameter.java
new file mode 100644
index 0000000..2c77e3b
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockRequestParameter.java
@@ -0,0 +1,105 @@
+/*
+ * 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.sling.servlethelpers;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.sling.api.request.RequestParameter;
+
+/**
+ * Mock implementation of {@link RequestParameter}.
+ */
+class MockRequestParameter implements RequestParameter {
+
+    private String name;
+    private String encoding = "UTF-8";
+    private String value;
+
+    private byte[] content;
+
+    public MockRequestParameter(String name, String value) {
+        this.value = value;
+        this.content = null;
+    }
+    void setName(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+    }
+
+    public String getEncoding() {
+        return this.encoding;
+    }
+
+    public byte[] get() {
+        if (content == null) {
+            try {
+                content = getString().getBytes(getEncoding());
+            } catch (Exception e) {
+                // UnsupportedEncodingException, IllegalArgumentException
+                content = getString().getBytes();
+            }
+        }
+        return content;
+    }
+
+    public String getContentType() {
+        // none known for www-form-encoded parameters
+        return null;
+    }
+
+    public InputStream getInputStream() {
+        return new ByteArrayInputStream(this.get());
+    }
+
+    public String getFileName() {
+        // no original file name
+        return null;
+    }
+
+    public long getSize() {
+        return this.get().length;
+    }
+
+    public String getString() {
+        return value;
+    }
+
+    public String getString(String encoding) throws UnsupportedEncodingException {
+        return new String(this.get(), encoding);
+    }
+
+    public boolean isFormField() {
+        // www-form-encoded are always form fields
+        return true;
+    }
+
+    public String toString() {
+        return this.getString();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockRequestParameterMap.java b/src/main/java/org/apache/sling/servlethelpers/MockRequestParameterMap.java
new file mode 100644
index 0000000..93e25de
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockRequestParameterMap.java
@@ -0,0 +1,101 @@
+/*
+ * 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.sling.servlethelpers;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.request.RequestParameterMap;
+
+/**
+ * Mock implementation of {@link RequestParameterMap}.
+ */
+class MockRequestParameterMap implements RequestParameterMap {
+    
+    private final Map<String,RequestParameter[]> delegate = new HashMap<String, RequestParameter[]>();
+    
+    public RequestParameter getValue(String name) {
+        RequestParameter[] params = getValues(name);
+        return (params != null && params.length > 0) ? params[0] : null;
+    }
+
+    public RequestParameter[] getValues(String name) {
+        return delegate.get(name);
+    }
+
+    public int size() {
+        return delegate.size();
+    }
+
+    public boolean isEmpty() {
+        return delegate.isEmpty();
+    }
+
+    public boolean containsKey(Object key) {
+        return delegate.containsKey(key);
+    }
+
+    public boolean containsValue(Object value) {
+        return delegate.containsValue(value);
+    }
+
+    public RequestParameter[] get(Object key) {
+        return delegate.get(key);
+    }
+
+    public RequestParameter[] put(String key, RequestParameter[] value) {
+        return delegate.put(key, value);
+    }
+
+    public RequestParameter[] remove(Object key) {
+        return delegate.remove(key);
+    }
+
+    public void putAll(Map<? extends String, ? extends RequestParameter[]> m) {
+        delegate.putAll(m);
+    }
+
+    public void clear() {
+        delegate.clear();
+    }
+
+    public Set<String> keySet() {
+        return delegate.keySet();
+    }
+
+    public Collection<RequestParameter[]> values() {
+        return delegate.values();
+    }
+
+    public Set<java.util.Map.Entry<String, RequestParameter[]>> entrySet() {
+        return delegate.entrySet();
+    }
+
+    public boolean equals(Object o) {
+        return delegate.equals(o);
+    }
+
+    public int hashCode() {
+        return delegate.hashCode();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockRequestPathInfo.java b/src/main/java/org/apache/sling/servlethelpers/MockRequestPathInfo.java
new file mode 100644
index 0000000..4c6439d
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockRequestPathInfo.java
@@ -0,0 +1,89 @@
+/*
+ * 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.sling.servlethelpers;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.resource.Resource;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * Mock {@link RequestPathInfo} implementation.
+ */
+@ConsumerType
+public class MockRequestPathInfo implements RequestPathInfo {
+
+    private String extension;
+    private String resourcePath;
+    private String selectorString;
+    private String suffix;
+
+    @Override
+    public String getExtension() {
+        return this.extension;
+    }
+
+    @Override
+    public String getResourcePath() {
+        return this.resourcePath;
+    }
+
+    @Override
+    public String[] getSelectors() {
+        if (StringUtils.isEmpty(this.selectorString)) {
+            return new String[0];
+        } else {
+            return StringUtils.split(this.selectorString, ".");
+        }
+    }
+
+    @Override
+    public String getSelectorString() {
+        return this.selectorString;
+    }
+
+    @Override
+    public String getSuffix() {
+        return this.suffix;
+    }
+
+    public void setExtension(final String extension) {
+        this.extension = extension;
+    }
+
+    public void setResourcePath(final String resourcePath) {
+        this.resourcePath = resourcePath;
+    }
+
+    public void setSelectorString(final String selectorString) {
+        this.selectorString = selectorString;
+    }
+
+    public void setSuffix(final String suffix) {
+        this.suffix = suffix;
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public Resource getSuffixResource() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockServletContext.java b/src/main/java/org/apache/sling/servlethelpers/MockServletContext.java
new file mode 100644
index 0000000..4741738
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockServletContext.java
@@ -0,0 +1,304 @@
+/*
+ * 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.sling.servlethelpers;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRegistration.Dynamic;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * Mock {@link ServletContext} implementation.
+ */
+@ConsumerType
+public class MockServletContext implements ServletContext {
+
+    @Override
+    public String getMimeType(final String file) {
+        return "application/octet-stream";
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public Object getAttribute(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServletContext getContext(final String uriPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getContextPath() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getInitParameter(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getMajorVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getMinorVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestDispatcher getNamedDispatcher(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRealPath(final String pPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public URL getResource(final String pPath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public InputStream getResourceAsStream(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<String> getResourcePaths(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getServerInfo() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Servlet getServlet(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getServletContextName() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<String> getServletNames() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<Servlet> getServlets() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void log(final String msg) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void log(final Exception exception, final String msg) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void log(final String msg, final Throwable throwable) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeAttribute(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setAttribute(final String name, final Object object) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getEffectiveMajorVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getEffectiveMinorVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean setInitParameter(final String name, final String value) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Dynamic addServlet(final String servletName, final String className) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Dynamic addServlet(final String servletName, final Servlet servlet) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Dynamic addServlet(final String servletName, final Class<? extends Servlet> servletClass) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends Servlet> T createServlet(final Class<T> clazz) throws ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServletRegistration getServletRegistration(final String servletName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, ? extends ServletRegistration> getServletRegistrations() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(final String filterName, final String className) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(final String filterName, final Filter filter) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FilterRegistration.Dynamic addFilter(final String filterName, final Class<? extends Filter> filterClass) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends Filter> T createFilter(final Class<T> clazz) throws ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public FilterRegistration getFilterRegistration(final String filterName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SessionCookieConfig getSessionCookieConfig() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setSessionTrackingModes(final Set<SessionTrackingMode> sessionTrackingModes) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void addListener(final String pClassName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends EventListener> void addListener(final T listener) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void addListener(final Class<? extends EventListener> listenerClass) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends EventListener> T createListener(final Class<T> clazz) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public JspConfigDescriptor getJspConfigDescriptor() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ClassLoader getClassLoader() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void declareRoles(final String... roleNames) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockSlingHttpServletRequest.java b/src/main/java/org/apache/sling/servlethelpers/MockSlingHttpServletRequest.java
new file mode 100644
index 0000000..88404fd
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockSlingHttpServletRequest.java
@@ -0,0 +1,805 @@
+/*
+ * 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.sling.servlethelpers;
+
+import static org.apache.sling.servlethelpers.MockSlingHttpServletResponse.CHARSET_SEPARATOR;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.ListResourceBundle;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.Part;
+
+import org.apache.commons.collections.IteratorUtils;
+import org.apache.commons.lang3.CharEncoding;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.adapter.SlingAdaptable;
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.request.RequestParameterMap;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.request.RequestProgressTracker;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.servlets.HttpConstants;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * Mock {@link SlingHttpServletRequest} implementation.
+ */
+@ConsumerType
+public class MockSlingHttpServletRequest extends SlingAdaptable implements SlingHttpServletRequest {
+
+    private final ResourceResolver resourceResolver;
+    private final RequestPathInfo requestPathInfo;
+    private Map<String, Object> attributeMap = new HashMap<String, Object>();
+    private Map<String, String[]> parameterMap = new LinkedHashMap<String, String[]>();
+    private HttpSession session;
+    private Resource resource;
+    private String contextPath;
+    private String queryString;
+    private String scheme = "http";
+    private String serverName = "localhost";
+    private int serverPort = 80;
+    private String method = HttpConstants.METHOD_GET;
+    private final HeaderSupport headerSupport = new HeaderSupport();
+    private final CookieSupport cookieSupport = new CookieSupport();
+    private String contentType;
+    private String characterEncoding;
+    private byte[] content;
+    private String remoteUser;
+    private String remoteAddr;
+    private String remoteHost;
+    private int remotePort;
+
+    private MockRequestDispatcherFactory requestDispatcherFactory;
+    
+    protected static final ResourceBundle EMPTY_RESOURCE_BUNDLE = new ListResourceBundle() {
+        @Override
+        protected Object[][] getContents() {
+            return new Object[0][0];
+        }
+    };
+
+    /**
+     * @param resourceResolver Resource resolver
+     */
+    public MockSlingHttpServletRequest(ResourceResolver resourceResolver) {
+        this.resourceResolver = resourceResolver;
+        this.requestPathInfo = newMockRequestPathInfo();
+    }
+
+    protected MockHttpSession newMockHttpSession() {
+        return new MockHttpSession();
+    }
+
+    protected MockRequestPathInfo newMockRequestPathInfo() {
+        return new MockRequestPathInfo();
+    }
+
+    @Override
+    public ResourceResolver getResourceResolver() {
+        return this.resourceResolver;
+    }
+
+    @Override
+    public HttpSession getSession() {
+        return getSession(true);
+    }
+
+    @Override
+    public HttpSession getSession(boolean create) {
+        if (this.session == null && create) {
+            this.session = newMockHttpSession();
+        }
+        return this.session;
+    }
+    
+    @Override
+    public RequestPathInfo getRequestPathInfo() {
+        return this.requestPathInfo;
+    }
+
+    @Override
+    public Object getAttribute(String name) {
+        return this.attributeMap.get(name);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        return IteratorUtils.asEnumeration(this.attributeMap.keySet().iterator());
+    }
+
+    @Override
+    public void removeAttribute(String name) {
+        this.attributeMap.remove(name);
+    }
+
+    @Override
+    public void setAttribute(String name, Object object) {
+        this.attributeMap.put(name, object);
+    }
+
+    @Override
+    public Resource getResource() {
+        return this.resource;
+    }
+
+    public void setResource(Resource resource) {
+        this.resource = resource;
+    }
+
+    @Override
+    public String getParameter(String name) {
+        Object object = this.parameterMap.get(name);
+        if (object instanceof String) {
+            return (String) object;
+        } else if (object instanceof String[]) {
+            String[] values = (String[]) object;
+            if (values.length > 0) {
+                return values[0];
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Map<String, String[]> getParameterMap() {
+        return this.parameterMap;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Enumeration<String> getParameterNames() {
+        return IteratorUtils.asEnumeration(this.parameterMap.keySet().iterator());
+    }
+
+    @Override
+    public String[] getParameterValues(String name) { // NOPMD
+        Object object = this.parameterMap.get(name);
+        if (object instanceof String) {
+            return new String[] { (String) object };
+        } else if (object instanceof String[]) {
+            return (String[]) object;
+        }
+        return null; // NOPMD
+    }
+
+    /**
+     * @param parameterMap Map of parameters
+     */
+    public void setParameterMap(Map<String, Object> parameterMap) {
+        this.parameterMap.clear();
+        for (Map.Entry<String, Object> entry : parameterMap.entrySet()) {
+            String key = entry.getKey();
+            Object value = entry.getValue();
+            if (value instanceof String[]) {
+                this.parameterMap.put(key, (String[]) value);
+            } else if (value != null) {
+                this.parameterMap.put(key, new String[] { value.toString() });
+            } else {
+                this.parameterMap.put(key, null);
+            }
+        }
+        try {
+            this.queryString = formatQueryString(this.parameterMap);
+        } catch (UnsupportedEncodingException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private String formatQueryString(Map<String, String[]> map) throws UnsupportedEncodingException {
+        StringBuilder querystring = new StringBuilder();
+        for (Map.Entry<String, String[]> entry : this.parameterMap.entrySet()) {
+            if (entry.getValue() != null) {
+                for (String value : entry.getValue()) {
+                    if (querystring.length() != 0) {
+                        querystring.append('&');
+                    }
+                    querystring.append(URLEncoder.encode(entry.getKey(), CharEncoding.UTF_8));
+                    querystring.append('=');
+                    if (value != null) {
+                        querystring.append(URLEncoder.encode(value, CharEncoding.UTF_8));
+                    }
+                }
+            }
+        }
+        if (querystring.length() > 0) {
+            return querystring.toString();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public Locale getLocale() {
+        return Locale.US;
+    }
+
+    @Override
+    public String getContextPath() {
+        return this.contextPath;
+    }
+
+    /**
+     * @param contextPath Webapp context path
+     */
+    public void setContextPath(String contextPath) {
+        this.contextPath = contextPath;
+    }
+
+    /**
+     * @param queryString Query string (with proper URL encoding)
+     */
+    public void setQueryString(String queryString) {
+        this.queryString = queryString;
+        try {
+            parseQueryString(this.parameterMap, this.queryString);
+        } catch (UnsupportedEncodingException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private void parseQueryString(Map<String, String[]> map, String query) throws UnsupportedEncodingException {
+        Map<String, List<String>> queryPairs = new LinkedHashMap<String, List<String>>();
+        String[] pairs = query.split("&");
+        for (String pair : pairs) {
+            int idx = pair.indexOf('=');
+            String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), CharEncoding.UTF_8) : pair;
+            if (!queryPairs.containsKey(key)) {
+                queryPairs.put(key, new ArrayList<String>());
+            }
+            String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1),
+                    CharEncoding.UTF_8) : null;
+            queryPairs.get(key).add(value);
+        }
+        map.clear();
+        for (Map.Entry<String, List<String>> entry : queryPairs.entrySet()) {
+            map.put(entry.getKey(), entry.getValue().toArray(new String[entry.getValue().size()]));
+        }
+    }
+
+    @Override
+    public String getQueryString() {
+        return this.queryString;
+    }
+
+    @Override
+    public String getScheme() {
+        return this.scheme;
+    }
+
+    public void setScheme(String scheme) {
+        this.scheme = scheme;
+    }
+
+    @Override
+    public String getServerName() {
+        return this.serverName;
+    }
+
+    public void setServerName(String serverName) {
+        this.serverName = serverName;
+    }
+
+    @Override
+    public int getServerPort() {
+        return this.serverPort;
+    }
+
+    public void setServerPort(int serverPort) {
+        this.serverPort = serverPort;
+    }
+
+    @Override
+    public boolean isSecure() {
+        return StringUtils.equals("https", getScheme());
+    }
+
+    @Override
+    public String getMethod() {
+        return this.method;
+    }
+
+    public void setMethod(String method) {
+        this.method = method;
+    }
+
+    @Override
+    public long getDateHeader(String name) {
+        return headerSupport.getDateHeader(name);
+    }
+
+    @Override
+    public String getHeader(String name) {
+        return headerSupport.getHeader(name);
+    }
+
+    @Override
+    public Enumeration<String> getHeaderNames() {
+        return HeaderSupport.toEnumeration(headerSupport.getHeaderNames());
+    }
+
+    @Override
+    public Enumeration<String> getHeaders(String name) {
+        return HeaderSupport.toEnumeration(headerSupport.getHeaders(name));
+    }
+
+    @Override
+    public int getIntHeader(String name) {
+        return headerSupport.getIntHeader(name);
+    }
+
+    /**
+     * Add header, keep existing ones with same name.
+     * @param name Header name
+     * @param value Header value
+     */
+    public void addHeader(String name, String value) {
+        headerSupport.addHeader(name, value);
+    }
+
+    /**
+     * Add header, keep existing ones with same name.
+     * @param name Header name
+     * @param value Header value
+     */
+    public void addIntHeader(String name, int value) {
+        headerSupport.addIntHeader(name, value);
+    }
+
+    /**
+     * Add header, keep existing ones with same name.
+     * @param name Header name
+     * @param date Header value
+     */
+    public void addDateHeader(String name, long date) {
+        headerSupport.addDateHeader(name, date);
+    }
+
+    /**
+     * Set header, overwrite existing ones with same name.
+     * @param name Header name
+     * @param value Header value
+     */
+    public void setHeader(String name, String value) {
+        headerSupport.setHeader(name, value);
+    }
+
+    /**
+     * Set header, overwrite existing ones with same name.
+     * @param name Header name
+     * @param value Header value
+     */
+    public void setIntHeader(String name, int value) {
+        headerSupport.setIntHeader(name, value);
+    }
+
+    /**
+     * Set header, overwrite existing ones with same name.
+     * @param name Header name
+     * @param date Header value
+     */
+    public void setDateHeader(String name, long date) {
+        headerSupport.setDateHeader(name, date);
+    }
+
+    @Override
+    public Cookie getCookie(String name) {
+        return cookieSupport.getCookie(name);
+    }
+
+    @Override
+    public Cookie[] getCookies() {
+        return cookieSupport.getCookies();
+    }
+
+    /**
+     * Set cookie
+     * @param cookie Cookie
+     */
+    public void addCookie(Cookie cookie) {
+        cookieSupport.addCookie(cookie);
+    }
+
+    @Override
+    public ResourceBundle getResourceBundle(Locale locale) {
+        return getResourceBundle(null, locale);
+    }
+
+    @Override
+    public ResourceBundle getResourceBundle(String baseName, Locale locale) {
+        return EMPTY_RESOURCE_BUNDLE;
+    }
+
+    @Override
+    public RequestParameter getRequestParameter(String name) {
+        String value = getParameter(name);
+        if (value != null) {
+            return new MockRequestParameter(name, value);
+        }
+        return null;
+    }
+
+    @Override
+    public RequestParameterMap getRequestParameterMap() {
+        MockRequestParameterMap map = new MockRequestParameterMap();
+        for (Map.Entry<String,String[]> entry : getParameterMap().entrySet()) {
+            map.put(entry.getKey(), getRequestParameters(entry.getKey()));
+        }
+        return map;
+    }
+
+    @Override
+    public RequestParameter[] getRequestParameters(String name) {
+        String[] values = getParameterValues(name);
+        if (values == null) {
+            return null;
+        }
+        RequestParameter[] requestParameters = new RequestParameter[values.length];
+        for (int i = 0; i < values.length; i++) {
+            requestParameters[i] = new MockRequestParameter(name, values[i]);
+        }
+        return requestParameters;
+    }
+
+    // part of Sling API 2.7
+    public List<RequestParameter> getRequestParameterList() {
+        List<RequestParameter> params = new ArrayList<RequestParameter>();
+        for (RequestParameter[] requestParameters : getRequestParameterMap().values()) {
+            params.addAll(Arrays.asList(requestParameters));
+        }
+        return params;
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+        return this.characterEncoding;
+    }
+
+    @Override
+    public void setCharacterEncoding(String charset) {
+        this.characterEncoding = charset;
+    }
+
+    @Override
+    public String getContentType() {
+        if (this.contentType == null) {
+            return null;
+        } else {
+            return this.contentType
+                    + (StringUtils.isNotBlank(characterEncoding) ? CHARSET_SEPARATOR + characterEncoding : "");
+        }
+    }
+    
+    public void setContentType(String type) {
+        this.contentType = type;
+        if (StringUtils.contains(this.contentType, CHARSET_SEPARATOR)) {
+            this.characterEncoding = StringUtils.substringAfter(this.contentType, CHARSET_SEPARATOR);
+            this.contentType = StringUtils.substringBefore(this.contentType, CHARSET_SEPARATOR);
+        }
+    }
+
+    @Override
+    public ServletInputStream getInputStream() {
+        if (content == null) {
+            return null;
+        }
+        return new ServletInputStream() {
+            private final InputStream is = new ByteArrayInputStream(content);
+            @Override
+            public int read() throws IOException {
+                return is.read();
+            }
+        };  
+    }
+
+    @Override
+    public int getContentLength() {
+        if (content == null) {
+            return 0;
+        }
+        return content.length;
+    }
+    
+    public void setContent(byte[] content) {
+        this.content = content;
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path) {
+        if (requestDispatcherFactory == null) {
+            throw new IllegalStateException("Please provdide a MockRequestDispatcherFactory (setRequestDispatcherFactory).");
+        }
+        return requestDispatcherFactory.getRequestDispatcher(path,  null);
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path, RequestDispatcherOptions options) {
+        if (requestDispatcherFactory == null) {
+            throw new IllegalStateException("Please provdide a MockRequestDispatcherFactory (setRequestDispatcherFactory).");
+        }
+        return requestDispatcherFactory.getRequestDispatcher(path,  options);
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(Resource resource) {
+        if (requestDispatcherFactory == null) {
+            throw new IllegalStateException("Please provdide a MockRequestDispatcherFactory (setRequestDispatcherFactory).");
+        }
+        return requestDispatcherFactory.getRequestDispatcher(resource, null);
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(Resource resource, RequestDispatcherOptions options) {
+        if (requestDispatcherFactory == null) {
+            throw new IllegalStateException("Please provdide a MockRequestDispatcherFactory (setRequestDispatcherFactory).");
+        }
+        return requestDispatcherFactory.getRequestDispatcher(resource, options);
+    }
+    
+    public void setRequestDispatcherFactory(MockRequestDispatcherFactory requestDispatcherFactory) {
+        this.requestDispatcherFactory = requestDispatcherFactory;
+    }
+
+    @Override
+    public String getRemoteUser() {
+        return remoteUser;
+    }
+
+    public void setRemoteUser(String remoteUser) {
+        this.remoteUser = remoteUser;
+    }
+
+    @Override
+    public String getRemoteAddr() {
+        return remoteAddr;
+    }
+
+    public void setRemoteAddr(String remoteAddr) {
+        this.remoteAddr = remoteAddr;
+    }
+
+    @Override
+    public String getRemoteHost() {
+        return remoteHost;
+    }
+
+    public void setRemoteHost(String remoteHost) {
+        this.remoteHost = remoteHost;
+    }
+
+    @Override
+    public int getRemotePort() {
+        return remotePort;
+    }
+
+    public void setRemotePort(int remotePort) {
+        this.remotePort = remotePort;
+    }
+
+    
+    // --- unsupported operations ---
+
+    @Override
+    public RequestProgressTracker getRequestProgressTracker() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getResponseContentType() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<String> getResponseContentTypes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getAuthType() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getPathInfo() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getPathTranslated() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRequestURI() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public StringBuffer getRequestURL() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRequestedSessionId() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getServletPath() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Principal getUserPrincipal() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromCookie() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromURL() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRequestedSessionIdFromUrl() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isRequestedSessionIdValid() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isUserInRole(String role) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getLocalAddr() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getLocalName() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getLocalPort() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<Locale> getLocales() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getProtocol() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public BufferedReader getReader() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRealPath(String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean authenticate(HttpServletResponse response) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void login(String pUsername, String password) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void logout() throws ServletException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Collection<Part> getParts() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Part getPart(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AsyncContext startAsync() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isAsyncStarted() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isAsyncSupported() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AsyncContext getAsyncContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public DispatcherType getDispatcherType() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockSlingHttpServletResponse.java b/src/main/java/org/apache/sling/servlethelpers/MockSlingHttpServletResponse.java
new file mode 100644
index 0000000..6acfb7c
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockSlingHttpServletResponse.java
@@ -0,0 +1,282 @@
+/*
+ * 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.sling.servlethelpers;
+
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.adapter.SlingAdaptable;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * Mock {@link SlingHttpServletResponse} implementation.
+ */
+@ConsumerType
+public class MockSlingHttpServletResponse extends SlingAdaptable implements SlingHttpServletResponse {
+
+    static final String CHARSET_SEPARATOR = ";charset=";
+
+    private String contentType;
+    private String characterEncoding;
+    private int contentLength;
+    private int status = HttpServletResponse.SC_OK;
+    private int bufferSize = 1024 * 8;
+    private boolean isCommitted;
+    private final HeaderSupport headerSupport = new HeaderSupport();
+    private final ResponseBodySupport bodySupport = new ResponseBodySupport();
+    private final CookieSupport cookieSupport = new CookieSupport();
+
+    @Override
+    public String getContentType() {
+        if (this.contentType == null) {
+            return null;
+        } else {
+            return this.contentType
+                    + (StringUtils.isNotBlank(characterEncoding) ? CHARSET_SEPARATOR + characterEncoding : "");
+        }
+    }
+
+    @Override
+    public void setContentType(String type) {
+        this.contentType = type;
+        if (StringUtils.contains(this.contentType, CHARSET_SEPARATOR)) {
+            this.characterEncoding = StringUtils.substringAfter(this.contentType, CHARSET_SEPARATOR);
+            this.contentType = StringUtils.substringBefore(this.contentType, CHARSET_SEPARATOR);
+        }
+    }
+
+    @Override
+    public void setCharacterEncoding(String charset) {
+        this.characterEncoding = charset;
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+        return this.characterEncoding;
+    }
+
+    @Override
+    public void setContentLength(int len) {
+        this.contentLength = len;
+    }
+
+    public int getContentLength() {
+        return this.contentLength;
+    }
+
+    @Override
+    public void setStatus(int sc, String sm) {
+        setStatus(sc);
+    }
+
+    @Override
+    public void setStatus(int sc) {
+        this.status = sc;
+    }
+
+    @Override
+    public int getStatus() {
+        return this.status;
+    }
+
+    @Override
+    public void sendError(int sc, String msg) {
+        setStatus(sc);
+    }
+
+    @Override
+    public void sendError(int sc) {
+        setStatus(sc);
+    }
+
+    @Override
+    public void sendRedirect(String location) {
+        setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+        setHeader("Location", location);
+    }
+
+    @Override
+    public void addHeader(String name, String value) {
+        headerSupport.addHeader(name, value);
+    }
+
+    @Override
+    public void addIntHeader(String name, int value) {
+        headerSupport.addIntHeader(name, value);
+    }
+
+    @Override
+    public void addDateHeader(String name, long date) {
+        headerSupport.addDateHeader(name, date);
+    }
+
+    @Override
+    public void setHeader(String name, String value) {
+        headerSupport.setHeader(name, value);
+    }
+
+    @Override
+    public void setIntHeader(String name, int value) {
+        headerSupport.setIntHeader(name, value);
+    }
+
+    @Override
+    public void setDateHeader(String name, long date) {
+        headerSupport.setDateHeader(name, date);
+    }
+
+    @Override
+    public boolean containsHeader(String name) {
+        return headerSupport.containsHeader(name);
+    }
+
+    @Override
+    public String getHeader(String name) {
+        return headerSupport.getHeader(name);
+    }
+
+    @Override
+    public Collection<String> getHeaders(String name) {
+        return headerSupport.getHeaders(name);
+    }
+
+    @Override
+    public Collection<String> getHeaderNames() {
+        return headerSupport.getHeaderNames();
+    }
+
+    @Override
+    public PrintWriter getWriter() {
+        return bodySupport.getWriter(getCharacterEncoding());
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() {
+        return bodySupport.getOutputStream();
+    }
+
+    @Override
+    public void reset() {
+        if (isCommitted()) {
+            throw new IllegalStateException("Response already committed.");
+        }
+        bodySupport.reset();
+        headerSupport.reset();
+        cookieSupport.reset();
+        status = HttpServletResponse.SC_OK;
+        contentLength = 0;
+    }
+
+    @Override
+    public void resetBuffer() {
+        if (isCommitted()) {
+            throw new IllegalStateException("Response already committed.");
+        }
+        bodySupport.reset();
+    }
+
+    @Override
+    public int getBufferSize() {
+        return this.bufferSize;
+    }
+
+    @Override
+    public void setBufferSize(int size) {
+        this.bufferSize = size;
+    }
+
+    @Override
+    public void flushBuffer() {
+        isCommitted = true;
+    }
+
+    @Override
+    public boolean isCommitted() {
+        return isCommitted;
+    }
+
+    public byte[] getOutput() {
+        return bodySupport.getOutput();
+    }
+
+    public String getOutputAsString() {
+        return bodySupport.getOutputAsString(getCharacterEncoding());
+    }
+
+    @Override
+    public void addCookie(Cookie cookie) {
+        cookieSupport.addCookie(cookie);
+    }
+
+    /**
+     * Get cookie
+     * @param name Cookie name
+     * @return Cookie or null
+     */
+    public Cookie getCookie(String name) {
+        return cookieSupport.getCookie(name);
+    }
+
+    /**
+     * Get cookies
+     * @return Cookies array or null if no cookie defined
+     */
+    public Cookie[] getCookies() {
+        return cookieSupport.getCookies();
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public Locale getLocale() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setLocale(Locale loc) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String encodeRedirectUrl(String url) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String encodeRedirectURL(String url) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String encodeUrl(String url) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String encodeURL(String url) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/ResponseBodySupport.java b/src/main/java/org/apache/sling/servlethelpers/ResponseBodySupport.java
new file mode 100644
index 0000000..c19f2c2
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/ResponseBodySupport.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.sling.servlethelpers;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+
+import javax.servlet.ServletOutputStream;
+
+import org.apache.commons.lang3.CharEncoding;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Manage response body content.
+ */
+class ResponseBodySupport {
+
+    private ByteArrayOutputStream outputStream;
+    private ServletOutputStream servletOutputStream;
+    private PrintWriter printWriter;
+
+    public ResponseBodySupport() {
+        reset();
+    }
+
+    public void reset() {
+        outputStream = new ByteArrayOutputStream();
+        servletOutputStream = null;
+        printWriter = null;
+    }
+
+    public ServletOutputStream getOutputStream() {
+        if (servletOutputStream == null) {
+            servletOutputStream = new ServletOutputStream() {
+                @Override
+                public void write(int b) throws IOException {
+                    outputStream.write(b);
+                }
+            };
+        }
+        return servletOutputStream;
+    }
+
+    public PrintWriter getWriter(String charset) {
+        if (printWriter == null) {
+            try {
+                printWriter = new PrintWriter(new OutputStreamWriter(getOutputStream(), defaultCharset(charset)));
+            } catch (UnsupportedEncodingException ex) {
+                throw new RuntimeException("Unsupported encoding: " + defaultCharset(charset), ex);
+            }
+        }
+        return printWriter;
+    }
+
+    public byte[] getOutput() {
+        if (servletOutputStream != null) {
+            try {
+                servletOutputStream.flush();
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+        return outputStream.toByteArray();
+    }
+
+    public String getOutputAsString(String charset) {
+        if (printWriter != null) {
+            printWriter.flush();
+        }
+        try {
+            return new String(getOutput(), defaultCharset(charset));
+        } catch (UnsupportedEncodingException ex) {
+            throw new RuntimeException("Unsupported encoding: " + defaultCharset(charset), ex);
+        }
+    }
+    
+    private String defaultCharset(String charset) {
+        return StringUtils.defaultString(charset, CharEncoding.UTF_8);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/package-info.java b/src/main/java/org/apache/sling/servlethelpers/package-info.java
new file mode 100644
index 0000000..1497ab6
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+/**
+ * Mock implementation of selected Servlet-related Sling APIs.
+ */
+@aQute.bnd.annotation.Version("1.0")
+package org.apache.sling.servlethelpers;
diff --git a/src/test/java/org/apache/sling/servlethelpers/MockHttpSessionTest.java b/src/test/java/org/apache/sling/servlethelpers/MockHttpSessionTest.java
new file mode 100644
index 0000000..980253c
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlethelpers/MockHttpSessionTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.sling.servlethelpers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.sling.servlethelpers.MockHttpSession;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MockHttpSessionTest {
+
+    private MockHttpSession httpSession;
+
+    @Before
+    public void setUp() throws Exception {
+        httpSession = new MockHttpSession();
+    }
+
+    @Test
+    public void testServletContext() {
+        assertNotNull(httpSession.getServletContext());
+    }
+
+    @Test
+    public void testId() {
+        assertNotNull(httpSession.getId());
+    }
+
+    @Test
+    public void testCreationTime() {
+        assertNotNull(httpSession.getCreationTime());
+    }
+
+    @Test
+    public void testAttributes() {
+        httpSession.setAttribute("attr1", "value1");
+        assertTrue(httpSession.getAttributeNames().hasMoreElements());
+        assertEquals("value1", httpSession.getAttribute("attr1"));
+        httpSession.removeAttribute("attr1");
+        assertFalse(httpSession.getAttributeNames().hasMoreElements());
+    }
+
+    @Test
+    public void testValues() {
+        httpSession.putValue("attr1", "value1");
+        assertEquals(1, httpSession.getValueNames().length);
+        assertEquals("value1", httpSession.getValue("attr1"));
+        httpSession.removeValue("attr1");
+        assertEquals(0, httpSession.getValueNames().length);
+    }
+
+    @Test
+    public void testInvalidate() {
+        httpSession.invalidate();
+        assertTrue(httpSession.isInvalidated());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testInvalidateStateCheck() {
+        httpSession.invalidate();
+        httpSession.getAttribute("attr1");
+    }
+
+    @Test
+    public void testIsNew() {
+        assertTrue(httpSession.isNew());
+        httpSession.setNew(false);
+        assertFalse(httpSession.isNew());
+   }
+
+    @Test
+    public void testGetLastAccessedTime() {
+        assertNotNull(httpSession.getLastAccessedTime());
+    }
+
+    @Test
+    public void testGetMaxInactiveInterval() {
+        assertTrue(httpSession.getMaxInactiveInterval() > 0);
+        httpSession.setMaxInactiveInterval(123);
+        assertEquals(123, httpSession.getMaxInactiveInterval());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/servlethelpers/MockRequestPathInfoTest.java b/src/test/java/org/apache/sling/servlethelpers/MockRequestPathInfoTest.java
new file mode 100644
index 0000000..f94c380
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlethelpers/MockRequestPathInfoTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.sling.servlethelpers;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.apache.sling.servlethelpers.MockRequestPathInfo;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MockRequestPathInfoTest {
+
+    private MockRequestPathInfo requestPathInfo;
+
+    @Before
+    public void setUp() throws Exception {
+        this.requestPathInfo = new MockRequestPathInfo();
+    }
+
+    @Test
+    public void testExtension() {
+        assertNull(this.requestPathInfo.getExtension());
+        this.requestPathInfo.setExtension("ext");
+        assertEquals("ext", this.requestPathInfo.getExtension());
+    }
+
+    @Test
+    public void testResourcePath() {
+        assertNull(this.requestPathInfo.getResourcePath());
+        this.requestPathInfo.setResourcePath("/path");
+        assertEquals("/path", this.requestPathInfo.getResourcePath());
+    }
+
+    @Test
+    public void testSelector() {
+        assertNull(this.requestPathInfo.getSelectorString());
+        assertEquals(0, this.requestPathInfo.getSelectors().length);
+        this.requestPathInfo.setSelectorString("aa.bb");
+        assertEquals("aa.bb", this.requestPathInfo.getSelectorString());
+        assertEquals(2, this.requestPathInfo.getSelectors().length);
+        assertArrayEquals(new String[] { "aa", "bb" }, this.requestPathInfo.getSelectors());
+    }
+
+    @Test
+    public void testSuffix() {
+        assertNull(this.requestPathInfo.getSuffix());
+        this.requestPathInfo.setSuffix("/suffix");
+        assertEquals("/suffix", this.requestPathInfo.getSuffix());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/servlethelpers/MockServletContextTest.java b/src/test/java/org/apache/sling/servlethelpers/MockServletContextTest.java
new file mode 100644
index 0000000..131b531
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlethelpers/MockServletContextTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.sling.servlethelpers;
+
+import static org.junit.Assert.assertEquals;
+
+import javax.servlet.ServletContext;
+
+import org.apache.sling.servlethelpers.MockServletContext;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MockServletContextTest {
+
+    private ServletContext servletContext;
+
+    @Before
+    public void setUp() throws Exception {
+        this.servletContext = new MockServletContext();
+    }
+
+    @Test
+    public void testGetMimeType() {
+        assertEquals("application/octet-stream", this.servletContext.getMimeType("any"));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/servlethelpers/MockSlingHttpServletRequestTest.java b/src/test/java/org/apache/sling/servlethelpers/MockSlingHttpServletRequestTest.java
new file mode 100644
index 0000000..28d6b50
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlethelpers/MockSlingHttpServletRequestTest.java
@@ -0,0 +1,328 @@
+/*
+ * 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.sling.servlethelpers;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Calendar;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.CharEncoding;
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.servlets.HttpConstants;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MockSlingHttpServletRequestTest {
+
+    @Mock
+    private ResourceResolver resourceResolver;
+    @Mock
+    private Resource resource;
+
+    private MockSlingHttpServletRequest request;
+
+    @Before
+    public void setUp() throws Exception {
+        request = new MockSlingHttpServletRequest(resourceResolver);
+    }
+    
+    @Test
+    public void testResourceResolver() {
+        assertSame(resourceResolver, request.getResourceResolver());
+    }
+
+    @Test
+    public void testDefaultResourceResolver() {
+        assertNotNull(request.getResourceResolver());
+    }
+
+    @Test
+    public void testSession() {
+        HttpSession session = request.getSession(false);
+        assertNull(session);
+        session = request.getSession();
+        assertNotNull(session);
+    }
+
+    @Test
+    public void testRequestPathInfo() {
+        assertNotNull(request.getRequestPathInfo());
+    }
+
+    @Test
+    public void testAttributes() {
+        request.setAttribute("attr1", "value1");
+        assertTrue(request.getAttributeNames().hasMoreElements());
+        assertEquals("value1", request.getAttribute("attr1"));
+        request.removeAttribute("attr1");
+        assertFalse(request.getAttributeNames().hasMoreElements());
+    }
+
+    @Test
+    public void testResource() {
+        assertNull(request.getResource());
+        request.setResource(resource);
+        assertSame(resource, request.getResource());
+    }
+
+    @Test
+    public void testContextPath() {
+        assertNull(request.getContextPath());
+        request.setContextPath("/ctx");
+        assertEquals("/ctx", request.getContextPath());
+    }
+
+    @Test
+    public void testLocale() {
+        assertEquals(Locale.US, request.getLocale());
+    }
+
+    @Test
+    public void testQueryString() throws UnsupportedEncodingException {
+        assertNull(request.getQueryString());
+        assertEquals(0, request.getParameterMap().size());
+        assertFalse(request.getParameterNames().hasMoreElements());
+
+        request.setQueryString("param1=123&param2=" + URLEncoder.encode("äöü߀!:!", CharEncoding.UTF_8)
+                + "&param3=a&param3=b");
+
+        assertNotNull(request.getQueryString());
+        assertEquals(3, request.getParameterMap().size());
+        assertTrue(request.getParameterNames().hasMoreElements());
+        assertEquals("123", request.getParameter("param1"));
+        assertEquals("äöü߀!:!", request.getParameter("param2"));
+        assertArrayEquals(new String[] { "a", "b" }, request.getParameterValues("param3"));
+
+        Map<String, Object> paramMap = new LinkedHashMap<String, Object>();
+        paramMap.put("p1", "a");
+        paramMap.put("p2", new String[] { "b", "c" });
+        paramMap.put("p3", null);
+        paramMap.put("p4", new String[] { null });
+        paramMap.put("p5", 22);
+        request.setParameterMap(paramMap);
+
+        assertEquals("p1=a&p2=b&p2=c&p4=&p5=22", request.getQueryString());
+    }
+
+    @Test
+    public void testSchemeSecure() {
+        assertEquals("http", request.getScheme());
+        assertFalse(request.isSecure());
+
+        request.setScheme("https");
+        assertEquals("https", request.getScheme());
+        assertTrue(request.isSecure());
+    }
+
+    @Test
+    public void testServerNamePort() {
+        assertEquals("localhost", request.getServerName());
+        assertEquals(80, request.getServerPort());
+
+        request.setServerName("myhost");
+        request.setServerPort(12345);
+        assertEquals("myhost", request.getServerName());
+        assertEquals(12345, request.getServerPort());
+    }
+
+    @Test
+    public void testMethod() {
+        assertEquals(HttpConstants.METHOD_GET, request.getMethod());
+
+        request.setMethod(HttpConstants.METHOD_POST);
+        assertEquals(HttpConstants.METHOD_POST, request.getMethod());
+    }
+
+    @Test
+    public void testHeaders() {
+        assertFalse(request.getHeaderNames().hasMoreElements());
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.MILLISECOND, 0);
+        long dateValue = calendar.getTimeInMillis();
+
+        request.addHeader("header1", "value1");
+        request.addIntHeader("header2", 5);
+        request.addDateHeader("header3", dateValue);
+
+        assertEquals("value1", request.getHeader("header1"));
+        assertEquals(5, request.getIntHeader("header2"));
+        assertEquals(dateValue, request.getDateHeader("header3"));
+
+        request.setHeader("header1", "value2");
+        request.addIntHeader("header2", 10);
+
+        Enumeration<String> header1Values = request.getHeaders("header1");
+        assertEquals("value2", header1Values.nextElement());
+        assertFalse(header1Values.hasMoreElements());
+
+        Enumeration<String> header2Values = request.getHeaders("header2");
+        assertEquals("5", header2Values.nextElement());
+        assertEquals("10", header2Values.nextElement());
+        assertFalse(header2Values.hasMoreElements());
+    }
+
+    @Test
+    public void testCookies() {
+        assertNull(request.getCookies());
+
+        request.addCookie(new Cookie("cookie1", "value1"));
+        request.addCookie(new Cookie("cookie2", "value2"));
+
+        assertEquals("value1", request.getCookie("cookie1").getValue());
+
+        Cookie[] cookies = request.getCookies();
+        assertEquals(2, cookies.length);
+        assertEquals("value1", cookies[0].getValue());
+        assertEquals("value2", cookies[1].getValue());
+    }
+    
+    @Test
+    public void testDefaultResourceBundle() {
+        ResourceBundle bundle = request.getResourceBundle(Locale.US);
+        assertNotNull(bundle);
+        assertFalse(bundle.getKeys().hasMoreElements());
+    }
+
+    @Test
+    public void testRequestParameter() throws Exception {
+        request.setQueryString("param1=123&param2=" + URLEncoder.encode("äöü߀!:!", CharEncoding.UTF_8)
+                + "&param3=a&param3=b");
+
+        assertEquals(3, request.getRequestParameterMap().size());
+        assertEquals(4, request.getRequestParameterList().size());
+        assertEquals("123", request.getRequestParameter("param1").getString());
+        assertEquals("äöü߀!:!", request.getRequestParameter("param2").getString());
+        assertEquals("a",request.getRequestParameters("param3")[0].getString());
+        assertEquals("b",request.getRequestParameters("param3")[1].getString());
+
+        assertNull(request.getRequestParameter("unknown"));
+        assertNull(request.getRequestParameters("unknown"));
+    }
+
+    @Test
+    public void testContentTypeCharset() throws Exception {
+        assertNull(request.getContentType());
+        assertNull(request.getCharacterEncoding());
+
+        request.setContentType("image/gif");
+        assertEquals("image/gif", request.getContentType());
+        assertNull(request.getCharacterEncoding());
+        
+        request.setContentType("text/plain;charset=UTF-8");
+        assertEquals("text/plain;charset=UTF-8", request.getContentType());
+        assertEquals(CharEncoding.UTF_8, request.getCharacterEncoding());
+        
+        request.setCharacterEncoding(CharEncoding.ISO_8859_1);
+        assertEquals("text/plain;charset=ISO-8859-1", request.getContentType());
+        assertEquals(CharEncoding.ISO_8859_1, request.getCharacterEncoding());
+    }
+
+    @Test
+    public void testContent() throws Exception {
+        assertEquals(0, request.getContentLength());
+        assertNull(request.getInputStream());
+        
+        byte[] data = new byte[] { 0x01,0x02,0x03 };
+        request.setContent(data);
+
+        assertEquals(data.length, request.getContentLength());
+        assertArrayEquals(data, IOUtils.toByteArray(request.getInputStream()));
+    }
+
+    @Test
+    public void testGetRequestDispatcher() {
+        MockRequestDispatcherFactory requestDispatcherFactory = mock(MockRequestDispatcherFactory.class);
+        RequestDispatcher requestDispatcher = mock(RequestDispatcher.class);
+        when(requestDispatcherFactory.getRequestDispatcher(any(Resource.class), any(RequestDispatcherOptions.class))).thenReturn(requestDispatcher);
+        when(requestDispatcherFactory.getRequestDispatcher(any(String.class), any(RequestDispatcherOptions.class))).thenReturn(requestDispatcher);
+        
+        request.setRequestDispatcherFactory(requestDispatcherFactory);
+        
+        assertSame(requestDispatcher, request.getRequestDispatcher("/path"));
+        assertSame(requestDispatcher, request.getRequestDispatcher("/path", new RequestDispatcherOptions()));
+        assertSame(requestDispatcher, request.getRequestDispatcher(resource));
+        assertSame(requestDispatcher, request.getRequestDispatcher(resource, new RequestDispatcherOptions()));
+    }
+    
+    @Test(expected = IllegalStateException.class)
+    public void testGetRequestDispatcherWithoutFactory() {
+        request.getRequestDispatcher("/path");
+    }
+    
+    @Test
+    public void testGetRemoteUser() {
+        assertNull(null, request.getRemoteUser());
+        
+        request.setRemoteUser("admin");
+        assertEquals("admin", request.getRemoteUser());
+    }
+
+    @Test
+    public void testGetRemoteAddr() throws Exception {
+        assertNull(null, request.getRemoteAddr());
+        
+        request.setRemoteAddr("1.2.3.4");
+        assertEquals("1.2.3.4", request.getRemoteAddr());
+    }
+
+    @Test
+    public void testGetRemoteHost() throws Exception {
+        assertNull(null, request.getRemoteHost());
+        
+        request.setRemoteHost("host1");
+        assertEquals("host1", request.getRemoteHost());
+    }
+
+    @Test
+    public void testGetRemotePort() throws Exception {
+        assertEquals(0, request.getRemotePort());
+        
+        request.setRemotePort(1234);
+        assertEquals(1234, request.getRemotePort());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/servlethelpers/MockSlingHttpServletResponseTest.java b/src/test/java/org/apache/sling/servlethelpers/MockSlingHttpServletResponseTest.java
new file mode 100644
index 0000000..54dae46
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlethelpers/MockSlingHttpServletResponseTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.sling.servlethelpers;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.CharEncoding;
+import org.apache.sling.servlethelpers.MockSlingHttpServletResponse;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MockSlingHttpServletResponseTest {
+
+    private MockSlingHttpServletResponse response;
+
+    @Before
+    public void setUp() throws Exception {
+        this.response = new MockSlingHttpServletResponse();
+    }
+
+    @Test
+    public void testContentTypeCharset() throws Exception {
+        assertNull(response.getContentType());
+        assertNull(response.getCharacterEncoding());
+
+        response.setContentType("image/gif");
+        assertEquals("image/gif", response.getContentType());
+        assertNull(response.getCharacterEncoding());
+        
+        response.setContentType("text/plain;charset=UTF-8");
+        assertEquals("text/plain;charset=UTF-8", response.getContentType());
+        assertEquals(CharEncoding.UTF_8, response.getCharacterEncoding());
+        
+        response.setCharacterEncoding(CharEncoding.ISO_8859_1);
+        assertEquals("text/plain;charset=ISO-8859-1", response.getContentType());
+        assertEquals(CharEncoding.ISO_8859_1, response.getCharacterEncoding());
+    }
+
+    @Test
+    public void testContentLength() throws Exception {
+        assertEquals(0, response.getContentLength());
+
+        response.setContentLength(55);
+        assertEquals(55, response.getContentLength());
+    }
+
+    @Test
+    public void testHeaders() throws Exception {
+        assertEquals(0, response.getHeaderNames().size());
+
+        response.addHeader("header1", "value1");
+        response.addIntHeader("header2", 5);
+        response.addDateHeader("header3", System.currentTimeMillis());
+
+        assertEquals(3, response.getHeaderNames().size());
+        assertTrue(response.containsHeader("header1"));
+        assertEquals("value1", response.getHeader("header1"));
+        assertEquals("5", response.getHeader("header2"));
+        assertNotNull(response.getHeader("header3"));
+
+        response.setHeader("header1", "value2");
+        response.addIntHeader("header2", 10);
+
+        assertEquals(3, response.getHeaderNames().size());
+
+        Collection<String> header1Values = response.getHeaders("header1");
+        assertEquals(1, header1Values.size());
+        assertEquals("value2", header1Values.iterator().next());
+
+        Collection<String> header2Values = response.getHeaders("header2");
+        assertEquals(2, header2Values.size());
+        Iterator<String> header2Iterator = header2Values.iterator();
+        assertEquals("5", header2Iterator.next());
+        assertEquals("10", header2Iterator.next());
+
+        response.reset();
+        assertEquals(0, response.getHeaderNames().size());
+    }
+
+    @Test
+    public void testRedirect() throws Exception {
+        response.sendRedirect("/location.html");
+        assertEquals(HttpServletResponse.SC_MOVED_TEMPORARILY, response.getStatus());
+        assertEquals("/location.html", response.getHeader("Location"));
+    }
+
+    @Test
+    public void testSendError() throws Exception {
+        response.sendError(HttpServletResponse.SC_NOT_FOUND);
+        assertEquals(HttpServletResponse.SC_NOT_FOUND, response.getStatus());
+    }
+
+    @Test
+    public void testSetStatus() throws Exception {
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+
+        response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
+        assertEquals(HttpServletResponse.SC_BAD_GATEWAY, response.getStatus());
+
+        response.reset();
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+    }
+
+    @Test
+    public void testWriteStringContent() throws Exception {
+        final String TEST_CONTENT = "Der Jodelkaiser äöü߀ ᚠᛇᚻ";
+        response.setCharacterEncoding(CharEncoding.UTF_8);
+        response.getWriter().write(TEST_CONTENT);
+        assertEquals(TEST_CONTENT, response.getOutputAsString());
+
+        response.resetBuffer();
+        assertEquals(0, response.getOutputAsString().length());
+    }
+
+    @Test
+    public void testWriteBinaryContent() throws Exception {
+        final byte[] TEST_DATA = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
+        response.getOutputStream().write(TEST_DATA);
+        assertArrayEquals(TEST_DATA, response.getOutput());
+
+        response.resetBuffer();
+        assertEquals(0, response.getOutput().length);
+    }
+
+    @Test
+    public void testIsCommitted() throws Exception {
+        assertFalse(response.isCommitted());
+        response.flushBuffer();
+        assertTrue(response.isCommitted());
+    }
+
+    @Test
+    public void testCookies() {
+        assertNull(response.getCookies());
+
+        response.addCookie(new Cookie("cookie1", "value1"));
+        response.addCookie(new Cookie("cookie2", "value2"));
+
+        assertEquals("value1", response.getCookie("cookie1").getValue());
+
+        Cookie[] cookies = response.getCookies();
+        assertEquals(2, cookies.length);
+        assertEquals("value1", cookies[0].getValue());
+        assertEquals("value2", cookies[1].getValue());
+
+        response.reset();
+        assertNull(response.getCookies());
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.