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:21:10 UTC

[sling-org-apache-sling-testing-sling-mock] 01/26: SLING-4042 Donate sling-mock, jcr-mock, osgi-mock implementation

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

rombert pushed a commit to annotated tag org.apache.sling.testing.sling-mock-1.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-sling-mock.git

commit 2aa4712721ccad034be2a252aaf30fbd7cf5dbc3
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Mon Oct 13 11:54:39 2014 +0000

    SLING-4042 Donate sling-mock, jcr-mock, osgi-mock implementation
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/sling-mock@1631356 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            | 240 +++++++
 .../mock/sling/JcrMockResourceResolverAdapter.java |  44 ++
 .../testing/mock/sling/MockAdapterManager.java     |  81 +++
 .../mock/sling/MockJcrResourceResolverFactory.java | 117 ++++
 .../MockResourceResolverFactoryActivator.java      |  52 ++
 .../apache/sling/testing/mock/sling/MockSling.java | 167 +++++
 .../testing/mock/sling/MockSlingRepository.java    | 112 ++++
 .../testing/mock/sling/MockSlingScriptHelper.java  | 163 +++++
 .../sling/RRMockMockResourceResolverAdapter.java   |  52 ++
 .../testing/mock/sling/ResourceResolverType.java   |  86 +++
 .../sling/ThreadsafeMockAdapterManagerWrapper.java |  61 ++
 .../testing/mock/sling/builder/ContentBuilder.java | 108 ++++
 .../testing/mock/sling/builder/package-info.java   |  22 +
 .../context/ContextResourceResolverFactory.java    |  77 +++
 .../mock/sling/context/SlingContextImpl.java       | 401 ++++++++++++
 .../testing/mock/sling/context/package-info.java   |  23 +
 .../testing/mock/sling/junit/SlingContext.java     | 207 ++++++
 .../mock/sling/junit/SlingContextCallback.java     |  37 ++
 .../testing/mock/sling/junit/package-info.java     |  23 +
 .../testing/mock/sling/loader/ContentLoader.java   | 586 +++++++++++++++++
 .../testing/mock/sling/loader/package-info.java    |  23 +
 .../sling/testing/mock/sling/package-info.java     |  23 +
 .../mock/sling/services/MockMimeTypeService.java   |  78 +++
 .../sling/services/MockModelAdapterFactory.java    | 295 +++++++++
 .../sling/services/MockSlingSettingService.java    |  90 +++
 .../testing/mock/sling/servlet/CookieSupport.java  |  53 ++
 .../testing/mock/sling/servlet/HeaderSupport.java  | 175 ++++++
 .../mock/sling/servlet/MockHttpSession.java        | 129 ++++
 .../mock/sling/servlet/MockRequestPathInfo.java    |  86 +++
 .../mock/sling/servlet/MockServletContext.java     | 301 +++++++++
 .../sling/servlet/MockSlingHttpServletRequest.java | 696 +++++++++++++++++++++
 .../servlet/MockSlingHttpServletResponse.java      | 280 +++++++++
 .../mock/sling/servlet/ResponseBodySupport.java    |  93 +++
 .../testing/mock/sling/servlet/package-info.java   |  23 +
 .../sling/spi/ResourceResolverTypeAdapter.java     |  46 ++
 src/site/markdown/index.md                         |  67 ++
 src/site/markdown/resource-resolver-types.md       |  53 ++
 src/site/markdown/usage-content-loader.md          |  59 ++
 src/site/markdown/usage-mocks.md                   | 130 ++++
 .../testing/mock/sling/MockAdapterManagerTest.java |  69 ++
 .../mock/sling/MockSlingRepositoryTest.java        |  46 ++
 .../mock/sling/MockSlingScriptHelperTest.java      |  79 +++
 .../mock/sling/builder/ContentBuilderTest.java     |  61 ++
 .../mock/sling/context/SlingContextImplTest.java   | 224 +++++++
 .../jcrmock/loader/ContentLoaderBinaryTest.java    |  33 +
 .../jcrmock/loader/ContentLoaderJsonDamTest.java   |  30 +
 .../jcrmock/loader/ContentLoaderJsonTest.java      |  30 +
 .../jcrmock/resource/JcrResourceResolverTest.java  |  34 +
 .../resource/SlingCrudResourceResolverTest.java    |  34 +
 .../testing/mock/sling/junit/SlingContextTest.java |  66 ++
 .../loader/AbstractContentLoaderBinaryTest.java    | 121 ++++
 .../loader/AbstractContentLoaderJsonDamTest.java   |  85 +++
 .../loader/AbstractContentLoaderJsonTest.java      | 182 ++++++
 .../resource/AbstractJcrResourceResolverTest.java  | 168 +++++
 .../AbstractSlingCrudResourceResolverTest.java     | 214 +++++++
 .../rrmock/loader/ContentLoaderBinaryTest.java     |  38 ++
 .../rrmock/loader/ContentLoaderJsonDamTest.java    |  38 ++
 .../sling/rrmock/loader/ContentLoaderJsonTest.java |  38 ++
 .../resource/SlingCrudResourceResolverTest.java    |  38 ++
 .../sling/services/MockMimeTypeServiceTest.java    |  51 ++
 .../services/MockModelAdapterFactoryTest.java      | 142 +++++
 .../services/MockSlingSettingServiceTest.java      |  53 ++
 .../mock/sling/servlet/MockHttpSessionTest.java    |  74 +++
 .../sling/servlet/MockRequestPathInfoTest.java     |  68 ++
 .../mock/sling/servlet/MockServletContextTest.java |  42 ++
 .../servlet/MockSlingHttpServletRequestTest.java   | 216 +++++++
 .../servlet/MockSlingHttpServletResponseTest.java  | 168 +++++
 .../resources/json-import-samples/content.json     | 257 ++++++++
 src/test/resources/json-import-samples/dam.json    | 156 +++++
 src/test/resources/sample-image.gif                | Bin 0 -> 62 bytes
 70 files changed, 8214 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..d4ce536
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,240 @@
+<?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/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>22</version>
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.testing.sling-mock</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Sling Testing Sling Mock</name>
+    <description>Mock implementation of selected Sling APIs.</description>
+
+    <properties>
+        <sling.java.version>6</sling.java.version>
+    </properties>
+
+    <dependencies>
+  
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.osgi-mock</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.jcr-mock</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.resourceresolver-mock</artifactId>
+            <version>0.3.1-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.models.api</artifactId>
+            <version>1.1.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.models.impl</artifactId>
+            <version>1.1.0</version>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.4.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.resourceresolver</artifactId>
+            <version>1.1.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.api</artifactId>
+            <version>2.2.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.resource</artifactId>
+            <version>2.3.6</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.mime</artifactId>
+            <version>2.1.4</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.json</artifactId>
+            <version>2.0.6</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.2.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.classloader</artifactId>
+            <version>1.3.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.settings</artifactId>
+            <version>1.2.2</version>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+            <version>3.2.1</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.5</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+            <version>1.8.3</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <version>0.9.9-RC2</version>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>org.mortbay.jetty</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>3.0.20100224</version>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <version>1</version>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+            <version>2.0</version>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+  
+    <build>
+        <plugins>
+    
+          <!-- Publish test artifact -->
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-jar-plugin</artifactId>
+            <executions>
+              <execution>
+                <goals>
+                  <goal>test-jar</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+    
+          <plugin>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>maven-bundle-plugin</artifactId>
+            <extensions>true</extensions>
+          </plugin>
+    
+          <plugin>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>maven-scr-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>generate-scr-scrdescriptor</id>
+                <goals>
+                  <goal>scr</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+    
+        </plugins>
+    </build>
+  
+</project>
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/JcrMockResourceResolverAdapter.java b/src/main/java/org/apache/sling/testing/mock/sling/JcrMockResourceResolverAdapter.java
new file mode 100644
index 0000000..6aafe5d
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/JcrMockResourceResolverAdapter.java
@@ -0,0 +1,44 @@
+/*
+ * 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.testing.mock.sling;
+
+import javax.jcr.Repository;
+
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.testing.mock.jcr.MockJcr;
+import org.apache.sling.testing.mock.sling.spi.ResourceResolverTypeAdapter;
+
+/**
+ * Resource resolver type adapter for JCR Mocks implementation.
+ */
+class JcrMockResourceResolverAdapter implements ResourceResolverTypeAdapter {
+
+    @Override
+    public ResourceResolverFactory newResourceResolverFactory() {
+        return null;
+    }
+
+    @Override
+    public SlingRepository newSlingRepository() {
+        Repository repository = MockJcr.newRepository();
+        return new MockSlingRepository(repository);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/MockAdapterManager.java b/src/main/java/org/apache/sling/testing/mock/sling/MockAdapterManager.java
new file mode 100644
index 0000000..60b2082
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/MockAdapterManager.java
@@ -0,0 +1,81 @@
+/*
+ * 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.testing.mock.sling;
+
+import org.apache.sling.api.adapter.AdapterFactory;
+import org.apache.sling.api.adapter.AdapterManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Mock {@link AdapterManager} implementation.
+ */
+class MockAdapterManager implements AdapterManager {
+
+    /**
+     * OSGi bundle context to detect all services that implement
+     * {@link AdapterFactory}.
+     */
+    private BundleContext bundleContext;
+
+    /**
+     * Returns the adapted <code>adaptable</code> or <code>null</code> if the
+     * object cannot be adapted.
+     */
+    @Override
+    public <AdapterType> AdapterType getAdapter(final Object adaptable, final Class<AdapterType> type) {
+
+        // iterate over all adapter factories and try to adapt the object
+        if (this.bundleContext != null) {
+            try {
+                ServiceReference[] references = bundleContext
+                        .getServiceReferences(AdapterFactory.class.getName(), null);
+                for (ServiceReference serviceReference : references) {
+                    AdapterFactory adapterFactory = (AdapterFactory) bundleContext.getService(serviceReference);
+                    AdapterType instance = adapterFactory.getAdapter(adaptable, type);
+                    if (instance != null) {
+                        return instance;
+                    }
+                }
+            } catch (InvalidSyntaxException ex) {
+                throw new RuntimeException("Unable to get adapter factories.", ex);
+            }
+        }
+
+        // no matching adapter factory found
+        return null;
+    }
+
+    /**
+     * Sets bundle context.
+     * @param bundleContext Bundle context
+     */
+    public void setBundleContext(final BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    /**
+     * Removes bundle context reference.
+     */
+    public void clearBundleContext() {
+        this.bundleContext = null;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/MockJcrResourceResolverFactory.java b/src/main/java/org/apache/sling/testing/mock/sling/MockJcrResourceResolverFactory.java
new file mode 100644
index 0000000..8a9716b
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/MockJcrResourceResolverFactory.java
@@ -0,0 +1,117 @@
+/*
+ * 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.testing.mock.sling;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProviderFactory;
+import org.apache.sling.resourceresolver.impl.CommonResourceResolverFactoryImpl;
+import org.apache.sling.resourceresolver.impl.ResourceAccessSecurityTracker;
+import org.apache.sling.resourceresolver.impl.ResourceResolverImpl;
+import org.apache.sling.resourceresolver.impl.helper.ResourceResolverContext;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Mock {@link ResourceResolver} implementation. Simulates OSGi environment and
+ * intatiates real Sling ResourceResolver and JCR implementation, but with a
+ * mocked JCR repository implementation underneath.
+ */
+class MockJcrResourceResolverFactory implements ResourceResolverFactory {
+
+    private final SlingRepository slingRepository;
+
+    public MockJcrResourceResolverFactory(final SlingRepository repository) {
+        this.slingRepository = repository;
+    }
+
+    @Override
+    public ResourceResolver getResourceResolver(final Map<String, Object> authenticationInfo) throws LoginException {
+        // setup mock OSGi environment
+        Dictionary<String, Object> resourceProviderFactoryProps = new Hashtable<String, Object>();
+        resourceProviderFactoryProps.put(Constants.SERVICE_VENDOR, "sling-mock");
+        resourceProviderFactoryProps.put(Constants.SERVICE_DESCRIPTION, "sling-mock");
+        resourceProviderFactoryProps.put("resource.resolver.manglenamespaces", true);
+        ComponentContext componentContext = MockOsgi.newComponentContext(resourceProviderFactoryProps);
+
+        // setup mocked JCR environment
+        componentContext.getBundleContext()
+                .registerService(SlingRepository.class.getName(), this.slingRepository, null);
+
+        // setup real sling JCR resource provider implementation for use in
+        // mocked context
+        JcrResourceProviderFactory jcrResourceProviderFactory = new JcrResourceProviderFactory();
+        MockOsgi.injectServices(jcrResourceProviderFactory, componentContext.getBundleContext());
+        MockOsgi.activate(jcrResourceProviderFactory, componentContext.getBundleContext(),
+                ImmutableMap.<String, Object> of());
+        ResourceProvider resourceProvider = jcrResourceProviderFactory
+                .getAdministrativeResourceProvider(new HashMap<String, Object>());
+        Dictionary<Object, Object> resourceProviderProps = new Hashtable<Object, Object>();
+        resourceProviderProps.put(ResourceProvider.ROOTS, new String[] { "/" });
+        componentContext.getBundleContext().registerService(ResourceProvider.class.getName(), resourceProvider,
+                resourceProviderProps);
+        ServiceReference resourceProviderServiceReference = componentContext.getBundleContext().getServiceReference(
+                ResourceProvider.class.getName());
+
+        // setup real sling resource resolver implementation for use in mocked
+        // context
+        MockResourceResolverFactoryActivator activator = new MockResourceResolverFactoryActivator();
+        activator.bindResourceProvider(resourceProvider,
+                getServiceReferenceProperties(resourceProviderServiceReference));
+        activator.activate(componentContext);
+        CommonResourceResolverFactoryImpl commonFactoryImpl = new CommonResourceResolverFactoryImpl(activator);
+        ResourceResolverContext context = new ResourceResolverContext(true, null, new ResourceAccessSecurityTracker());
+        ResourceResolverImpl resourceResolver = new ResourceResolverImpl(commonFactoryImpl, context);
+        return resourceResolver;
+    }
+
+    private Map<String, Object> getServiceReferenceProperties(final ServiceReference serviceReference) {
+        Map<String, Object> props = new HashMap<String, Object>();
+        String[] keys = serviceReference.getPropertyKeys();
+        for (String key : keys) {
+            props.put(key, serviceReference.getProperty(key));
+        }
+        return props;
+    }
+
+    @Override
+    public ResourceResolver getAdministrativeResourceResolver(final Map<String, Object> authenticationInfo)
+            throws LoginException {
+        return getResourceResolver(authenticationInfo);
+    }
+
+    // part of Sling API 2.7
+    public ResourceResolver getServiceResourceResolver(final Map<String, Object> authenticationInfo)
+            throws LoginException {
+        return getResourceResolver(authenticationInfo);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/MockResourceResolverFactoryActivator.java b/src/main/java/org/apache/sling/testing/mock/sling/MockResourceResolverFactoryActivator.java
new file mode 100644
index 0000000..12dc624
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/MockResourceResolverFactoryActivator.java
@@ -0,0 +1,52 @@
+/*
+ * 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.testing.mock.sling;
+
+import java.util.Map;
+
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.api.resource.ResourceProviderFactory;
+import org.apache.sling.resourceresolver.impl.ResourceResolverFactoryActivator;
+import org.osgi.service.component.ComponentContext;
+
+/**
+ * Overrides some behavior of {@link ResourceResolverFactoryActivator} to allow
+ * usage in mocking.
+ */
+class MockResourceResolverFactoryActivator extends ResourceResolverFactoryActivator {
+
+    // make public
+    @Override
+    public void activate(final ComponentContext componentContext) {
+        super.activate(componentContext);
+    }
+
+    // make public
+    @Override
+    public void bindResourceProviderFactory(final ResourceProviderFactory provider, final Map<String, Object> props) {
+        super.bindResourceProviderFactory(provider, props);
+    }
+
+    // make public
+    @Override
+    public void bindResourceProvider(final ResourceProvider provider, final Map<String, Object> props) {
+        super.bindResourceProvider(provider, props);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/MockSling.java b/src/main/java/org/apache/sling/testing/mock/sling/MockSling.java
new file mode 100644
index 0000000..208a8d1
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/MockSling.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.adapter.SlingAdaptable;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse;
+import org.apache.sling.testing.mock.sling.spi.ResourceResolverTypeAdapter;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Factory for mock Sling objects.
+ */
+public final class MockSling {
+
+    /**
+     * Default resource resolver type is {@link ResourceResolverType#RESOURCERESOLVER_MOCK}.
+     */
+    public static final ResourceResolverType DEFAULT_RESOURCERESOLVER_TYPE = ResourceResolverType.RESOURCERESOLVER_MOCK;
+
+    private static final ThreadsafeMockAdapterManagerWrapper ADAPTER_MANAGER = new ThreadsafeMockAdapterManagerWrapper();
+    static {
+        // register mocked adapter manager
+        SlingAdaptable.setAdapterManager(ADAPTER_MANAGER);
+    }
+
+    private MockSling() {
+        // static methods only
+    }
+
+    /**
+     * Creates new sling resource resolver factory instance.
+     * @param type Type of underlying repository.
+     * @return Resource resolver factory instance
+     */
+    public static ResourceResolverFactory newResourceResolverFactory(final ResourceResolverType type) {
+        ResourceResolverTypeAdapter adapter = getResourceResolverTypeAdapter(type);
+        ResourceResolverFactory factory = adapter.newResourceResolverFactory();
+        if (factory == null) {
+            SlingRepository repository = adapter.newSlingRepository();
+            if (repository == null) {
+                throw new RuntimeException("Adapter neither provides resource resolver factory nor sling repository.");
+            }
+            factory = new MockJcrResourceResolverFactory(repository);
+        }
+        return factory;
+    }
+
+    private static ResourceResolverTypeAdapter getResourceResolverTypeAdapter(final ResourceResolverType type) {
+        try {
+            Class clazz = Class.forName(type.getResourceResolverTypeAdapterClass());
+            return (ResourceResolverTypeAdapter) clazz.newInstance();
+        } catch (ClassNotFoundException ex) {
+            throw new RuntimeException("Unable to instantiate resourcer resolver: "
+                    + type.getResourceResolverTypeAdapterClass()
+                    + (type.getArtifactCoordinates() != null ? "Make sure this maven dependency is included: "
+                            + type.getArtifactCoordinates() : ""), ex);
+        } catch (InstantiationException ex) {
+            throw new RuntimeException("Unable to instantiate resourcer resolver: "
+                    + type.getResourceResolverTypeAdapterClass()
+                    + (type.getArtifactCoordinates() != null ? "Make sure this maven dependency is included: "
+                            + type.getArtifactCoordinates() : ""), ex);
+        } catch (IllegalAccessException ex) {
+            throw new RuntimeException("Unable to instantiate resourcer resolver: "
+                    + type.getResourceResolverTypeAdapterClass()
+                    + (type.getArtifactCoordinates() != null ? "Make sure this maven dependency is included: "
+                            + type.getArtifactCoordinates() : ""), ex);
+        }
+    }
+
+    /**
+     * Creates new sling resource resolver factory instance using
+     * {@link #DEFAULT_RESOURCERESOLVER_TYPE}.
+     * @return Resource resolver factory instance
+     */
+    public static ResourceResolverFactory newResourceResolverFactory() {
+        return newResourceResolverFactory(DEFAULT_RESOURCERESOLVER_TYPE);
+    }
+
+    /**
+     * Creates new sling resource resolver instance.
+     * @param type Type of underlying repository.
+     * @return Resource resolver instance
+     */
+    public static ResourceResolver newResourceResolver(final ResourceResolverType type) {
+        ResourceResolverFactory factory = newResourceResolverFactory(type);
+        try {
+            return factory.getResourceResolver(null);
+        } catch (LoginException ex) {
+            throw new RuntimeException("Mock resource resolver factory implementation seems to require login.", ex);
+        }
+    }
+
+    /**
+     * Creates new sling resource resolver instance using
+     * {@link #DEFAULT_RESOURCERESOLVER_TYPE}.
+     * @return Resource resolver instance
+     */
+    public static ResourceResolver newResourceResolver() {
+        return newResourceResolver(DEFAULT_RESOURCERESOLVER_TYPE);
+    }
+
+    /**
+     * Creates a new sling script helper instance.
+     * @param request Request
+     * @param response Response
+     * @param bundleContext Bundle context
+     * @return Sling script helper instance
+     */
+    public static SlingScriptHelper newSlingScriptHelper(final SlingHttpServletRequest request,
+            final SlingHttpServletResponse response, final BundleContext bundleContext) {
+        return new MockSlingScriptHelper(request, response, bundleContext);
+    }
+
+    /**
+     * Creates a new sling script helper instance using
+     * {@link #DEFAULT_RESOURCERESOLVER_TYPE} for the resource resolver.
+     * @return Sling script helper instance
+     */
+    public static SlingScriptHelper newSlingScriptHelper() {
+        SlingHttpServletRequest request = new MockSlingHttpServletRequest(newResourceResolver());
+        SlingHttpServletResponse response = new MockSlingHttpServletResponse();
+        BundleContext bundleContext = MockOsgi.newBundleContext();
+        return newSlingScriptHelper(request, response, bundleContext);
+    }
+
+    /**
+     * Set bundle context for adapter manager. From this bundle context the
+     * adapter factories are detected.
+     * @param bundleContext OSGi bundle context
+     */
+    public static void setAdapterManagerBundleContext(final BundleContext bundleContext) {
+        ADAPTER_MANAGER.setBundleContext(bundleContext);
+    }
+
+    /**
+     * Clear adapter registrations..
+     */
+    public static void clearAdapterManagerBundleContext() {
+        ADAPTER_MANAGER.clearBundleContext();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/MockSlingRepository.java b/src/main/java/org/apache/sling/testing/mock/sling/MockSlingRepository.java
new file mode 100644
index 0000000..1e33847
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/MockSlingRepository.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling;
+
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.testing.mock.jcr.MockJcr;
+
+/**
+ * Mock {@link SlingRepository} implementation.
+ */
+class MockSlingRepository implements SlingRepository {
+
+    private final Repository delegate;
+
+    public MockSlingRepository(final Repository delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public Session loginAdministrative(final String workspace) throws RepositoryException {
+        return login();
+    }
+
+    @Override
+    public Session loginService(final String subServiceName, final String workspace) throws LoginException,
+            RepositoryException {
+        return login();
+    }
+
+    @Override
+    public String getDefaultWorkspace() {
+        return MockJcr.DEFAULT_WORKSPACE;
+    }
+
+    // delegated methods
+    @Override
+    public String[] getDescriptorKeys() {
+        return this.delegate.getDescriptorKeys();
+    }
+
+    @Override
+    public boolean isStandardDescriptor(final String key) {
+        return this.delegate.isStandardDescriptor(key);
+    }
+
+    @Override
+    public boolean isSingleValueDescriptor(final String key) {
+        return this.delegate.isSingleValueDescriptor(key);
+    }
+
+    @Override
+    public Value getDescriptorValue(final String key) {
+        return this.delegate.getDescriptorValue(key);
+    }
+
+    @Override
+    public Value[] getDescriptorValues(final String key) {
+        return this.delegate.getDescriptorValues(key);
+    }
+
+    @Override
+    public String getDescriptor(final String key) {
+        return this.delegate.getDescriptor(key);
+    }
+
+    @Override
+    public Session login(final Credentials credentials, final String workspaceName) throws LoginException,
+            NoSuchWorkspaceException, RepositoryException {
+        return this.delegate.login(credentials, workspaceName);
+    }
+
+    @Override
+    public Session login(final Credentials credentials) throws LoginException, RepositoryException {
+        return this.delegate.login(credentials);
+    }
+
+    @Override
+    public Session login(final String workspaceName) throws LoginException, NoSuchWorkspaceException,
+            RepositoryException {
+        return this.delegate.login(workspaceName);
+    }
+
+    @Override
+    public Session login() throws LoginException, RepositoryException {
+        return this.delegate.login();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/MockSlingScriptHelper.java b/src/main/java/org/apache/sling/testing/mock/sling/MockSlingScriptHelper.java
new file mode 100644
index 0000000..5882d9d
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/MockSlingScriptHelper.java
@@ -0,0 +1,163 @@
+/*
+ * 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.testing.mock.sling;
+
+import java.lang.reflect.Array;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.scripting.InvalidServiceFilterSyntaxException;
+import org.apache.sling.api.scripting.SlingScript;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Mock {@link SlingScriptHelper} implementation.
+ */
+class MockSlingScriptHelper implements SlingScriptHelper {
+
+    private final SlingHttpServletRequest request;
+    private final SlingHttpServletResponse response;
+    private final BundleContext bundleContext;
+
+    public MockSlingScriptHelper(final SlingHttpServletRequest request, final SlingHttpServletResponse response,
+            final BundleContext bundleContext) {
+        this.request = request;
+        this.response = response;
+        this.bundleContext = bundleContext;
+    }
+
+    @Override
+    public SlingHttpServletRequest getRequest() {
+        return this.request;
+    }
+
+    @Override
+    public SlingHttpServletResponse getResponse() {
+        return this.response;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <ServiceType> ServiceType getService(final Class<ServiceType> serviceType) {
+        ServiceReference serviceReference = this.bundleContext.getServiceReference(serviceType.getName());
+        if (serviceReference != null) {
+            return (ServiceType) this.bundleContext.getService(serviceReference);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <ServiceType> ServiceType[] getServices(final Class<ServiceType> serviceType, final String filter) {
+        try {
+            ServiceReference[] serviceReferences = this.bundleContext.getServiceReferences(serviceType.getName(),
+                    filter);
+            if (serviceReferences != null) {
+                ServiceType[] services = (ServiceType[]) Array.newInstance(serviceType, serviceReferences.length);
+                for (int i = 0; i < serviceReferences.length; i++) {
+                    services[i] = (ServiceType) this.bundleContext.getService(serviceReferences[i]);
+                }
+                return services;
+            } else {
+                return (ServiceType[]) ArrayUtils.EMPTY_OBJECT_ARRAY;
+            }
+        } catch (InvalidSyntaxException ex) {
+            throw new InvalidServiceFilterSyntaxException(filter, ex.getMessage(), ex);
+        }
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public void dispose() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void forward(final String path, final RequestDispatcherOptions requestDispatcherOptions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void forward(final String path, final String requestDispatcherOptions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void forward(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void forward(final Resource resource) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void forward(final Resource resource, final String requestDispatcherOptions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void forward(final Resource resource, final RequestDispatcherOptions requestDispatcherOptions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SlingScript getScript() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void include(final String path, final RequestDispatcherOptions requestDispatcherOptions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void include(final String path, final String requestDispatcherOptions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void include(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void include(final Resource resource) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void include(final Resource resource, final String requestDispatcherOptions) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void include(final Resource resource, final RequestDispatcherOptions requestDispatcherOptions) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/RRMockMockResourceResolverAdapter.java b/src/main/java/org/apache/sling/testing/mock/sling/RRMockMockResourceResolverAdapter.java
new file mode 100644
index 0000000..47c4a64
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/RRMockMockResourceResolverAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.testing.mock.sling;
+
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.testing.mock.sling.spi.ResourceResolverTypeAdapter;
+import org.apache.sling.testing.resourceresolver.MockResourceResolverFactory;
+import org.apache.sling.testing.resourceresolver.MockResourceResolverFactoryOptions;
+
+/**
+ * Resource resolver type adapter for Sling Resource Resolver Mock implementation.
+ */
+class RRMockMockResourceResolverAdapter implements ResourceResolverTypeAdapter {
+
+    private final MockResourceResolverFactoryOptions options;
+
+    /**
+     * Constructor
+     */
+    public RRMockMockResourceResolverAdapter() {
+        options = new MockResourceResolverFactoryOptions();
+        options.setMangleNamespacePrefixes(true);
+    }
+
+    @Override
+    public ResourceResolverFactory newResourceResolverFactory() {
+        return new MockResourceResolverFactory(options);
+    }
+
+    @Override
+    public SlingRepository newSlingRepository() {
+        return null;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/ResourceResolverType.java b/src/main/java/org/apache/sling/testing/mock/sling/ResourceResolverType.java
new file mode 100644
index 0000000..915c124
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/ResourceResolverType.java
@@ -0,0 +1,86 @@
+/*
+ * 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.testing.mock.sling;
+
+/**
+ * The resource resolver mock implementation supports different underlying
+ * repository implementations.
+ */
+public enum ResourceResolverType {
+
+    /**
+     * Uses Sling "resourceresolver-mock" implementation, no underlying JCR
+     * repository.
+     * <ul>
+     * <li>Simulates an In-Memory resource tree, does not provide adaptions to
+     * JCR.</li>
+     * <li>You can use it to make sure the code you want to test does not
+     * contain references to JCR API.</li>
+     * <li>Behaves slightly different from JCR resource mapping e.g. handling
+     * binary and date values.</li>
+     * <li>This resource resolver type is very fast.</li>
+     * </ul>
+     */
+    RESOURCERESOLVER_MOCK(RRMockMockResourceResolverAdapter.class.getName(), null),
+
+    /**
+     * Uses a simple JCR "in-memory" mock as underlying repository.
+     * <ul>
+     * <li>Uses the real Sling Resource Resolver and JCR Resource mapping
+     * implementation.</li>
+     * <li>The mock JCR implementation from Apache Sling is used.</li>
+     * <li>It supports the most important, but not all JCR features. Extended
+     * features like Versioning, Eventing, Search, Transaction handling etc. are
+     * not supported.</li>
+     * <li>This resource resolver type is quite fast.</li>
+     * </ul>
+     */
+    JCR_MOCK(JcrMockResourceResolverAdapter.class.getName(), null),
+
+    /**
+     * Uses a real JCR Jackrabbit repository.
+     * <ul>
+     * <li>Uses the real Sling Resource Resolver and JCR Resource mapping
+     * implementation.</li>
+     * <li>The JCR repository is started on first access, this may take some
+     * seconds.</li>
+     * <li>Beware: The repository is not cleared for each unit test, so make
+     * sure us use a unique node path for each unit test.</li>
+     * </ul>
+     */
+    JCR_JACKRABBIT("org.apache.sling.testing.mock.sling.jackrabbit.JackrabbitMockResourceResolverAdapter",
+            "org.apache.sling:org.apache.sling.testing.sling-mock-jackrabbit");
+
+    private final String resourceResolverTypeAdapterClass;
+    private final String artifactCoordinates;
+
+    private ResourceResolverType(final String resourceResolverTypeAdapterClass, final String artifactCoordinates) {
+        this.resourceResolverTypeAdapterClass = resourceResolverTypeAdapterClass;
+        this.artifactCoordinates = artifactCoordinates;
+    }
+
+    String getResourceResolverTypeAdapterClass() {
+        return this.resourceResolverTypeAdapterClass;
+    }
+
+    String getArtifactCoordinates() {
+        return this.artifactCoordinates;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/ThreadsafeMockAdapterManagerWrapper.java b/src/main/java/org/apache/sling/testing/mock/sling/ThreadsafeMockAdapterManagerWrapper.java
new file mode 100644
index 0000000..2e1f834
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/ThreadsafeMockAdapterManagerWrapper.java
@@ -0,0 +1,61 @@
+/*
+ * 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.testing.mock.sling;
+
+import org.apache.sling.api.adapter.AdapterManager;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Wrapper for {@link MockAdapterManager} which makes sure multiple unit tests
+ * running in parallel do not get in conflict with each other. Instead, a
+ * different {@link MockAdapterManager} is used per thread.
+ */
+class ThreadsafeMockAdapterManagerWrapper implements AdapterManager {
+
+    private static final ThreadLocal<MockAdapterManager> THREAD_LOCAL = new ThreadLocal<MockAdapterManager>() {
+        @Override
+        protected MockAdapterManager initialValue() {
+            return new MockAdapterManager();
+        }
+    };
+
+    @Override
+    public <AdapterType> AdapterType getAdapter(final Object adaptable, final Class<AdapterType> type) {
+        MockAdapterManager adapterManager = THREAD_LOCAL.get();
+        return adapterManager.getAdapter(adaptable, type);
+    }
+
+    /**
+     * Sets bundle context.
+     * @param bundleContext Bundle context
+     */
+    public void setBundleContext(final BundleContext bundleContext) {
+        MockAdapterManager adapterManager = THREAD_LOCAL.get();
+        adapterManager.setBundleContext(bundleContext);
+    }
+
+    /**
+     * Removes bundle context reference.
+     */
+    public void clearBundleContext() {
+        MockAdapterManager adapterManager = THREAD_LOCAL.get();
+        adapterManager.clearBundleContext();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/builder/ContentBuilder.java b/src/main/java/org/apache/sling/testing/mock/sling/builder/ContentBuilder.java
new file mode 100644
index 0000000..19b3979
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/builder/ContentBuilder.java
@@ -0,0 +1,108 @@
+/*
+ * 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.testing.mock.sling.builder;
+
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Helper class for building test content in the resource hierarchy with as less
+ * boilerplate code as possible.
+ */
+public class ContentBuilder {
+
+    static final String DUMMY_TEMPLATE = "/apps/sample/templates/template1";
+
+    protected final ResourceResolver resourceResolver;
+
+    /**
+     * @param resourceResolver Resource resolver
+     */
+    public ContentBuilder(ResourceResolver resourceResolver) {
+        this.resourceResolver = resourceResolver;
+    }
+
+    /**
+     * Create resource. If parent resource(s) do not exist they are created
+     * automatically using <code>nt:unstructured</code> nodes.
+     * @param path Page path
+     * @return Resource object
+     */
+    public final Resource resource(String path) {
+        return resource(path, ValueMap.EMPTY);
+    }
+
+    /**
+     * Create resource. If parent resource(s) do not exist they are created
+     * automatically using <code>nt:unstructured</code> nodes.
+     * @param path Page path
+     * @param properties Properties for resource.
+     * @return Resource object
+     */
+    public final Resource resource(String path, Map<String, Object> properties) {
+        String parentPath = ResourceUtil.getParent(path);
+        Resource parentResource = ensureResourceExists(parentPath);
+        String name = ResourceUtil.getName(path);
+        try {
+            return resourceResolver.create(parentResource, name, properties);
+        } catch (PersistenceException ex) {
+            throw new RuntimeException("Unable to create page at " + path, ex);
+        }
+    }
+
+    /**
+     * Ensure that a resource exists at the given path. If not, it is created
+     * using <code>nt:unstructured</code> node type.
+     * @param path Resource path
+     * @return Resource at path (existing or newly created)
+     */
+    protected final Resource ensureResourceExists(String path) {
+        if (StringUtils.isEmpty(path) || StringUtils.equals(path, "/")) {
+            return resourceResolver.getResource("/");
+        }
+        Resource resource = resourceResolver.getResource(path);
+        if (resource != null) {
+            return resource;
+        }
+        String parentPath = ResourceUtil.getParent(path);
+        String name = ResourceUtil.getName(path);
+        Resource parentResource = ensureResourceExists(parentPath);
+        try {
+            resource = resourceResolver.create(
+                    parentResource,
+                    name,
+                    ImmutableMap.<String, Object> builder()
+                            .put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED).build());
+            resourceResolver.commit();
+            return resource;
+        } catch (PersistenceException ex) {
+            throw new RuntimeException("Unable to create resource at " + path, ex);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/builder/package-info.java b/src/main/java/org/apache/sling/testing/mock/sling/builder/package-info.java
new file mode 100644
index 0000000..41f7c62
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/builder/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ * Content builder for creating test content.
+ */
+package org.apache.sling.testing.mock.sling.builder;
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/context/ContextResourceResolverFactory.java b/src/main/java/org/apache/sling/testing/mock/sling/context/ContextResourceResolverFactory.java
new file mode 100644
index 0000000..a8596ae
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/context/ContextResourceResolverFactory.java
@@ -0,0 +1,77 @@
+/*
+ * 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.testing.mock.sling.context;
+
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+
+/**
+ * Create resolve resolver instance and initialize it depending on it's type.
+ */
+final class ContextResourceResolverFactory {
+
+    private ContextResourceResolverFactory() {
+        // static methods only
+    }
+
+    public static ResourceResolver initializeResourceResolver(final ResourceResolverType resourceResolverType) {
+        try {
+            ResourceResolver resourceResolver = MockSling.newResourceResolver(resourceResolverType);
+
+            switch (resourceResolverType) {
+            case JCR_MOCK:
+                initializeJcrMock(resourceResolver);
+                break;
+            case JCR_JACKRABBIT:
+                initializeJcrJackrabbit(resourceResolver);
+                break;
+            case RESOURCERESOLVER_MOCK:
+                initializeResourceResolverMock(resourceResolver);
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid resource resolver type: " + resourceResolverType);
+            }
+
+            return resourceResolver;
+        } catch (Throwable ex) {
+            throw new RuntimeException("Unable to initialize " + resourceResolverType + " resource resolver.", ex);
+        }
+    }
+
+    private static void initializeJcrMock(final ResourceResolver resourceResolver) throws RepositoryException {
+        // register default namespaces
+        NamespaceRegistry namespaceRegistry = resourceResolver.adaptTo(Session.class).getWorkspace()
+                .getNamespaceRegistry();
+        namespaceRegistry.registerNamespace("sling", "http://sling.apache.org/jcr/sling/1.0");
+    }
+
+    private static void initializeJcrJackrabbit(final ResourceResolver resourceResolver) {
+        // TODO: register sling node types
+    }
+
+    private static void initializeResourceResolverMock(final ResourceResolver resourceResolver) {
+        // nothing to do
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/context/SlingContextImpl.java b/src/main/java/org/apache/sling/testing/mock/sling/context/SlingContextImpl.java
new file mode 100644
index 0000000..d72434d
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/context/SlingContextImpl.java
@@ -0,0 +1,401 @@
+/*
+ * 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.testing.mock.sling.context;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.api.adapter.AdapterFactory;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.commons.mime.MimeTypeService;
+import org.apache.sling.models.impl.FirstImplementationPicker;
+import org.apache.sling.models.impl.injectors.BindingsInjector;
+import org.apache.sling.models.impl.injectors.ChildResourceInjector;
+import org.apache.sling.models.impl.injectors.OSGiServiceInjector;
+import org.apache.sling.models.impl.injectors.RequestAttributeInjector;
+import org.apache.sling.models.impl.injectors.ResourcePathInjector;
+import org.apache.sling.models.impl.injectors.ResourceResolverInjector;
+import org.apache.sling.models.impl.injectors.SelfInjector;
+import org.apache.sling.models.impl.injectors.SlingObjectInjector;
+import org.apache.sling.models.impl.injectors.ValueMapInjector;
+import org.apache.sling.models.spi.ImplementationPicker;
+import org.apache.sling.models.spi.Injector;
+import org.apache.sling.settings.SlingSettingsService;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.builder.ContentBuilder;
+import org.apache.sling.testing.mock.sling.loader.ContentLoader;
+import org.apache.sling.testing.mock.sling.services.MockMimeTypeService;
+import org.apache.sling.testing.mock.sling.services.MockModelAdapterFactory;
+import org.apache.sling.testing.mock.sling.services.MockSlingSettingService;
+import org.apache.sling.testing.mock.sling.servlet.MockRequestPathInfo;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Defines Sling context objects with lazy initialization. Should not be used
+ * directly but via the {@link org.apache.sling.testing.mock.sling.junit.SlingContext} JUnit
+ * rule.
+ */
+public class SlingContextImpl {
+
+    // default to publish instance run mode
+    static final Set<String> DEFAULT_RUN_MODES = ImmutableSet.<String> builder().add("publish").build();
+
+    protected MockModelAdapterFactory modelAdapterFactory;
+    protected ResourceResolverType resourceResolverType;
+    protected ComponentContext componentContext;
+    protected ResourceResolver resourceResolver;
+    protected MockSlingHttpServletRequest request;
+    protected MockSlingHttpServletResponse response;
+    protected SlingScriptHelper slingScriptHelper;
+    protected ContentLoader contentLoader;
+    protected ContentBuilder contentBuilder;
+
+    /**
+     * @param resourceResolverType Resource resolver type
+     */
+    protected void setResourceResolverType(final ResourceResolverType resourceResolverType) {
+        this.resourceResolverType = resourceResolverType;
+    }
+
+    /**
+     * Setup actions before test method execution
+     */
+    protected void setUp() {
+        MockSling.setAdapterManagerBundleContext(bundleContext());
+        registerDefaultServices();
+    }
+
+    /**
+     * Default services that should be available for every unit test
+     */
+    protected void registerDefaultServices() {
+
+        // adapter factories
+        modelAdapterFactory = new MockModelAdapterFactory(componentContext());
+        registerService(AdapterFactory.class, modelAdapterFactory);
+
+        // sling models injectors
+        registerService(Injector.class, new BindingsInjector());
+        registerService(Injector.class, new ChildResourceInjector());
+        OSGiServiceInjector osgiServiceInjector = new OSGiServiceInjector();
+        osgiServiceInjector.activate(componentContext());
+        registerService(Injector.class, osgiServiceInjector);
+        registerService(Injector.class, new RequestAttributeInjector());
+        registerService(Injector.class, new ResourcePathInjector());
+        registerService(Injector.class, new ResourceResolverInjector());
+        registerService(Injector.class, new SelfInjector());
+        registerService(Injector.class, new SlingObjectInjector());
+        registerService(Injector.class, new ValueMapInjector());
+
+        // sling models implementation pickers
+        registerService(ImplementationPicker.class, new FirstImplementationPicker());
+
+        // other services
+        registerService(SlingSettingsService.class, new MockSlingSettingService(DEFAULT_RUN_MODES));
+        registerService(MimeTypeService.class, new MockMimeTypeService());
+    }
+
+    /**
+     * Teardown actions after test method execution
+     */
+    protected void tearDown() {
+
+        if (this.resourceResolver != null) {
+            // revert potential unsaved changes in resource resolver/JCR session
+            this.resourceResolver.revert();
+            Session session = this.resourceResolver.adaptTo(Session.class);
+            if (session != null) {
+                try {
+                    session.refresh(false);
+                } catch (RepositoryException ex) {
+                    // ignore
+                }
+            }
+        }
+
+        this.modelAdapterFactory = null;
+        this.componentContext = null;
+        this.resourceResolver = null;
+        this.request = null;
+        this.response = null;
+        this.slingScriptHelper = null;
+        this.contentLoader = null;
+        this.contentBuilder = null;
+
+        MockSling.clearAdapterManagerBundleContext();
+    }
+
+    /**
+     * @return Resource resolver type
+     */
+    public final ResourceResolverType resourceResolverType() {
+        return this.resourceResolverType;
+    }
+
+    /**
+     * @return OSGi component context
+     */
+    public final ComponentContext componentContext() {
+        if (this.componentContext == null) {
+            this.componentContext = MockOsgi.newComponentContext();
+        }
+        return this.componentContext;
+    }
+
+    /**
+     * @return OSGi Bundle context
+     */
+    public final BundleContext bundleContext() {
+        return componentContext().getBundleContext();
+    }
+
+    /**
+     * @return Resource resolver
+     */
+    public final ResourceResolver resourceResolver() {
+        if (this.resourceResolver == null) {
+            this.resourceResolver = createMockResourceResolver();
+        }
+        return this.resourceResolver;
+    }
+    
+    protected ResourceResolver createMockResourceResolver() {
+        return ContextResourceResolverFactory.initializeResourceResolver(resourceResolverType());
+    }
+
+    /**
+     * @return Sling request
+     */
+    public final MockSlingHttpServletRequest request() {
+        if (this.request == null) {
+            this.request = new MockSlingHttpServletRequest(this.resourceResolver());
+
+            // initialize sling bindings
+            SlingBindings bindings = new SlingBindings();
+            bindings.put(SlingBindings.REQUEST, this.request);
+            bindings.put(SlingBindings.RESPONSE, response());
+            bindings.put(SlingBindings.SLING, slingScriptHelper());
+            this.request.setAttribute(SlingBindings.class.getName(), bindings);
+        }
+        return this.request;
+    }
+
+    /**
+     * @return Request path info
+     */
+    public final MockRequestPathInfo requestPathInfo() {
+        return (MockRequestPathInfo) request().getRequestPathInfo();
+    }
+
+    /**
+     * @return Sling response
+     */
+    public final MockSlingHttpServletResponse response() {
+        if (this.response == null) {
+            this.response = new MockSlingHttpServletResponse();
+        }
+        return this.response;
+    }
+
+    /**
+     * @return Sling script helper
+     */
+    public final SlingScriptHelper slingScriptHelper() {
+        if (this.slingScriptHelper == null) {
+            this.slingScriptHelper = MockSling.newSlingScriptHelper(this.request(), this.response(),
+                    this.bundleContext());
+        }
+        return this.slingScriptHelper;
+    }
+
+    /**
+     * @return Content loader
+     */
+    public ContentLoader load() {
+        if (this.contentLoader == null) {
+            this.contentLoader = new ContentLoader(resourceResolver(), bundleContext());
+        }
+        return this.contentLoader;
+    }
+
+    /**
+     * @return Content builder for building test content
+     */
+    public ContentBuilder create() {
+        if (this.contentBuilder == null) {
+            this.contentBuilder = new ContentBuilder(resourceResolver());
+        }
+        return this.contentBuilder;
+    }
+
+    /**
+     * Registers a service in the mocked OSGi environment.
+     * @param service Service instance
+     * @return Registered service instance
+     */
+    public final <T> T registerService(final T service) {
+        return registerService(null, service, null);
+    }
+
+    /**
+     * Registers a service in the mocked OSGi environment.
+     * @param serviceClass Service class
+     * @param service Service instance
+     * @return Registered service instance
+     */
+    public final <T> T registerService(final Class<T> serviceClass, final T service) {
+        return registerService(serviceClass, service, null);
+    }
+
+    /**
+     * Registers a service in the mocked OSGi environment.
+     * @param serviceClass Service class
+     * @param service Service instance
+     * @param properties Service properties (optional)
+     * @return Registered service instance
+     */
+    public final <T> T registerService(final Class<T> serviceClass, final T service, final Map<String, Object> properties) {
+        Dictionary<String, Object> serviceProperties = null;
+        if (properties != null) {
+            serviceProperties = new Hashtable<String, Object>(properties);
+        }
+        bundleContext().registerService(serviceClass != null ? serviceClass.getName() : null, service,
+                serviceProperties);
+        return service;
+    }
+
+    /**
+     * Injects dependencies, activates and registers a service in the mocked
+     * OSGi environment.
+     * @param service Service instance
+     * @return Registered service instance
+     */
+    public final <T> T registerInjectActivateService(final T service) {
+        return registerInjectActivateService(service, ImmutableMap.<String, Object> of());
+    }
+
+    /**
+     * Injects dependencies, activates and registers a service in the mocked
+     * OSGi environment.
+     * @param service Service instance
+     * @param properties Service properties (optional)
+     * @return Registered service instance
+     */
+    public final <T> T registerInjectActivateService(final T service, final Map<String, Object> properties) {
+        MockOsgi.injectServices(service, bundleContext());
+        MockOsgi.activate(service, bundleContext(), properties);
+        registerService(null, service, null);
+        return service;
+    }
+
+    /**
+     * Lookup a single service
+     * @param serviceType The type (interface) of the service.
+     * @return The service instance, or null if the service is not available.
+     */
+    public final <ServiceType> ServiceType getService(final Class<ServiceType> serviceType) {
+        return slingScriptHelper().getService(serviceType);
+    }
+
+    /**
+     * Lookup one or several services
+     * @param serviceType The type (interface) of the service.
+     * @param filter An optional filter (LDAP-like, see OSGi spec)
+     * @return The services object or null.
+     * @throws InvalidServiceFilterSyntaxException If the <code>filter</code>
+     *             string is not a valid OSGi service filter string.
+     */
+    public final <ServiceType> ServiceType[] getServices(final Class<ServiceType> serviceType, final String filter) {
+        return slingScriptHelper().getServices(serviceType, filter);
+    }
+
+    /**
+     * @return Current resource
+     */
+    public final Resource currentResource() {
+        return request().getResource();
+    }
+
+    /**
+     * Set current resource in request.
+     * @param resourcePath Resource path
+     * @return Current resource
+     */
+    public final Resource currentResource(String resourcePath) {
+        if (resourcePath != null) {
+            Resource resource = resourceResolver().getResource(resourcePath);
+            if (resource == null) {
+                throw new IllegalArgumentException("Resource does not exist: " + resourcePath);
+            }
+            return currentResource(resource);
+        } else {
+            return currentResource((Resource) null);
+        }
+    }
+
+    /**
+     * Set current resource in request.
+     * @param resource Resource
+     * @return Current resource
+     */
+    public final Resource currentResource(Resource resource) {
+        request().setResource(resource);
+        return resource;
+    }
+
+    /**
+     * Scan classpaths for given package name (and sub packages) to scan for and
+     * register all classes with @Model annotation.
+     * @param packageName Java package name
+     */
+    public final void addModelsForPackage(String packageName) {
+        this.modelAdapterFactory.addModelsForPackage(packageName);
+    }
+
+    /**
+     * Set current run mode(s).
+     * @param runModes Run mode(s).
+     */
+    public final void runMode(String... runModes) {
+        Set<String> newRunModes = ImmutableSet.<String> builder().add(runModes).build();
+        ServiceReference ref = bundleContext().getServiceReference(SlingSettingsService.class.getName());
+        if (ref != null) {
+            MockSlingSettingService slingSettings = (MockSlingSettingService) bundleContext().getService(ref);
+            slingSettings.setRunModes(newRunModes);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/context/package-info.java b/src/main/java/org/apache/sling/testing/mock/sling/context/package-info.java
new file mode 100644
index 0000000..e229f1e
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/context/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.
+ */
+/**
+ * Sling context implementation for unit tests.
+ */
+package org.apache.sling.testing.mock.sling.context;
+
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/junit/SlingContext.java b/src/main/java/org/apache/sling/testing/mock/sling/junit/SlingContext.java
new file mode 100644
index 0000000..51ea608
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/junit/SlingContext.java
@@ -0,0 +1,207 @@
+/*
+ * 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.testing.mock.sling.junit;
+
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.context.SlingContextImpl;
+import org.junit.rules.ExternalResource;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * JUnit rule for setting up and tearing down Sling context objects for unit
+ * tests.
+ */
+public final class SlingContext extends SlingContextImpl implements TestRule {
+
+    private final SlingContextCallback setUpCallback;
+    private final SlingContextCallback tearDownCallback;
+    private final ResourceResolverType resourceResolverType;
+    private final TestRule delegate;
+
+    /**
+     * Initialize Sling context.
+     * <p>
+     * If context is initialized with:
+     * </p>
+     * <ul>
+     * <li>No resource resolver type - default is used
+     * {@link MockSling#DEFAULT_RESOURCERESOLVER_TYPE}.</li>
+     * <li>One resource resolver type - exactly this is used.</li>
+     * <li>More than one: all unit test methods are executed for all resource
+     * resolver types using {@link ListGenerator}.</li>
+     * </ul>
+     */
+    public SlingContext() {
+        this(null, null, null);
+    }
+
+    /**
+     * Initialize Sling context.
+     * <p>
+     * If context is initialized with:
+     * </p>
+     * <ul>
+     * <li>No resource resolver type - default is used
+     * {@link MockSling#DEFAULT_RESOURCERESOLVER_TYPE}.</li>
+     * <li>One resource resolver type - exactly this is used.</li>
+     * <li>More than one: all unit test methods are executed for all resource
+     * resolver types using {@link ListGenerator}.</li>
+     * </ul>
+     * @param resourceResolverType Resource resolver type.
+     */
+    public SlingContext(final ResourceResolverType resourceResolverType) {
+        this(null, null, resourceResolverType);
+    }
+
+    /**
+     * Initialize Sling context.
+     * <p>
+     * If context is initialized with:
+     * </p>
+     * <ul>
+     * <li>No resource resolver type - default is used
+     * {@link MockSling#DEFAULT_RESOURCERESOLVER_TYPE}.</li>
+     * <li>One resource resolver type - exactly this is used.</li>
+     * <li>More than one: all unit test methods are executed for all resource
+     * resolver types using {@link ListGenerator}.</li>
+     * </ul>
+     * @param setUpCallback Allows the application to register an own callback
+     *            function that is called after the built-in setup rules are
+     *            executed.
+     */
+    public SlingContext(final SlingContextCallback setUpCallback) {
+        this(setUpCallback, null, null);
+    }
+
+    /**
+     * Initialize Sling context.
+     * <p>
+     * If context is initialized with:
+     * </p>
+     * <ul>
+     * <li>No resource resolver type - default is used
+     * {@link MockSling#DEFAULT_RESOURCERESOLVER_TYPE}.</li>
+     * <li>One resource resolver type - exactly this is used.</li>
+     * <li>More than one: all unit test methods are executed for all resource
+     * resolver types using {@link ListGenerator}.</li>
+     * </ul>
+     * @param setUpCallback Allows the application to register an own callback
+     *            function that is called after the built-in setup rules are
+     *            executed.
+     * @param resourceResolverType Resource resolver type.
+     */
+    public SlingContext(final SlingContextCallback setUpCallback, final ResourceResolverType resourceResolverType) {
+        this(setUpCallback, null, resourceResolverType);
+    }
+
+    /**
+     * Initialize Sling context.
+     * <p>
+     * If context is initialized with:
+     * </p>
+     * <ul>
+     * <li>No resource resolver type - default is used
+     * {@link MockSling#DEFAULT_RESOURCERESOLVER_TYPE}.</li>
+     * <li>One resource resolver type - exactly this is used.</li>
+     * <li>More than one: all unit test methods are executed for all resource
+     * resolver types using {@link ListGenerator}.</li>
+     * </ul>
+     * @param setUpCallback Allows the application to register an own callback
+     *            function that is called after the built-in setup rules are
+     *            executed.
+     * @param tearDownCallback Allows the application to register an own
+     *            callback function that is called before the built-in teardown
+     *            rules are executed.
+     */
+    public SlingContext(final SlingContextCallback setUpCallback, final SlingContextCallback tearDownCallback) {
+        this(setUpCallback, tearDownCallback, null);
+    }
+    
+    /**
+     * Initialize Sling context.
+     * <p>
+     * If context is initialized with:
+     * </p>
+     * <ul>
+     * <li>No resource resolver type - default is used
+     * {@link MockSling#DEFAULT_RESOURCERESOLVER_TYPE}.</li>
+     * <li>One resource resolver type - exactly this is used.</li>
+     * <li>More than one: all unit test methods are executed for all resource
+     * resolver types using {@link ListGenerator}.</li>
+     * </ul>
+     * @param setUpCallback Allows the application to register an own callback
+     *            function that is called after the built-in setup rules are
+     *            executed.
+     * @param tearDownCallback Allows the application to register an own
+     *            callback function that is called before the built-in teardown
+     *            rules are executed.
+     * @param resourceResolverType Resource resolver type.
+     */
+    public SlingContext(final SlingContextCallback setUpCallback, final SlingContextCallback tearDownCallback,
+            final ResourceResolverType resourceResolverType) {
+
+        this.setUpCallback = setUpCallback;
+        this.tearDownCallback = tearDownCallback;
+        this.resourceResolverType = resourceResolverType;
+
+        // user default rule that directly executes each test method once
+        setResourceResolverType(this.resourceResolverType);
+        this.delegate = new ExternalResource() {
+            @Override
+            protected void before() {
+                SlingContext.this.setUp();
+                SlingContext.this.executeSetUpCallback();
+            }
+
+            @Override
+            protected void after() {
+                SlingContext.this.executeTearDownCallback();
+                SlingContext.this.tearDown();
+            }
+        };
+    }
+
+    @Override
+    public Statement apply(final Statement base, final Description description) {
+        return this.delegate.apply(base, description);
+    }
+
+    private void executeSetUpCallback() {
+        if (this.setUpCallback != null) {
+            try {
+                this.setUpCallback.execute(this);
+            } catch (Throwable ex) {
+                throw new RuntimeException("Executing setup callback failed.", ex);
+            }
+        }
+    }
+
+    private void executeTearDownCallback() {
+        if (this.tearDownCallback != null) {
+            try {
+                this.tearDownCallback.execute(this);
+            } catch (Throwable ex) {
+                throw new RuntimeException("Executing setup callback failed.", ex);
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/junit/SlingContextCallback.java b/src/main/java/org/apache/sling/testing/mock/sling/junit/SlingContextCallback.java
new file mode 100644
index 0000000..913b832
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/junit/SlingContextCallback.java
@@ -0,0 +1,37 @@
+/*
+ * 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.testing.mock.sling.junit;
+
+import java.io.IOException;
+
+import org.apache.sling.api.resource.PersistenceException;
+
+/**
+ * Callback-interface for application-specific setup and teardown operations to
+ * customize the {@link AemContext} JUnit rule.
+ */
+public interface SlingContextCallback {
+
+    /**
+     * Execute callback action
+     * @param context Sling context
+     */
+    void execute(SlingContext context) throws IOException, PersistenceException;
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/junit/package-info.java b/src/main/java/org/apache/sling/testing/mock/sling/junit/package-info.java
new file mode 100644
index 0000000..d30c3d0
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/junit/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.
+ */
+/**
+ * Rule for providing easy access to Sling context in JUnit tests.
+ */
+package org.apache.sling.testing.mock.sling.junit;
+
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/loader/ContentLoader.java b/src/main/java/org/apache/sling/testing/mock/sling/loader/ContentLoader.java
new file mode 100644
index 0000000..b434604
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/loader/ContentLoader.java
@@ -0,0 +1,586 @@
+/*
+ * 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.testing.mock.sling.loader;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.commons.json.jcr.JsonItemWriter;
+import org.apache.sling.commons.mime.MimeTypeService;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Imports JSON data and binary data into Sling resource hierarchy.
+ */
+public final class ContentLoader {
+
+    private static final String REFERENCE = "jcr:reference:";
+    private static final String PATH = "jcr:path:";
+    private static final String CONTENTTYPE_OCTET_STREAM = "application/octet-stream";
+
+    private static final Set<String> IGNORED_NAMES = ImmutableSet.of(JcrConstants.JCR_PRIMARYTYPE,
+            JcrConstants.JCR_MIXINTYPES, JcrConstants.JCR_UUID, JcrConstants.JCR_BASEVERSION,
+            JcrConstants.JCR_PREDECESSORS, JcrConstants.JCR_SUCCESSORS, JcrConstants.JCR_CREATED, "jcr:checkedOut");
+
+    private final ResourceResolver resourceResolver;
+    private final BundleContext bundleContext;
+    private final DateFormat calendarFormat;
+
+    /**
+     * @param resourceResolver Resource resolver
+     */
+    public ContentLoader(ResourceResolver resourceResolver) {
+        this(resourceResolver, null);
+    }
+
+    /**
+     * @param resourceResolver Resource resolver
+     * @param bundleContext Bundle context
+     */
+    public ContentLoader(ResourceResolver resourceResolver, BundleContext bundleContext) {
+        this.resourceResolver = resourceResolver;
+        this.bundleContext = bundleContext;
+        this.calendarFormat = new SimpleDateFormat(JsonItemWriter.ECMA_DATE_FORMAT, JsonItemWriter.DATE_FORMAT_LOCALE);
+    }
+
+    /**
+     * Import content of JSON file into repository.
+     * @param classpathResource Classpath resource URL for JSON content
+     * @param parentResource Parent resource
+     * @param childName Name of child resource to create with JSON content
+     * @return Resource
+     */
+    public Resource json(String classpathResource, Resource parentResource, String childName) {
+        InputStream is = ContentLoader.class.getResourceAsStream(classpathResource);
+        if (is == null) {
+            throw new IllegalArgumentException("Classpath resource not found: " + classpathResource);
+        }
+        try {
+            return json(is, parentResource, childName);
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Import content of JSON file into repository. Auto-creates parent
+     * hierarchies as nt:unstrucured nodes if missing.
+     * @param classpathResource Classpath resource URL for JSON content
+     * @param destPath Path to import the JSON content to
+     * @return Resource
+     */
+    public Resource json(String classpathResource, String destPath) {
+        InputStream is = ContentLoader.class.getResourceAsStream(classpathResource);
+        if (is == null) {
+            throw new IllegalArgumentException("Classpath resource not found: " + classpathResource);
+        }
+        try {
+            return json(is, destPath);
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Import content of JSON file into repository.
+     * @param inputStream Input stream with JSON content
+     * @param parentResource Parent resource
+     * @param childName Name of child resource to create with JSON content
+     * @return Resource
+     */
+    public Resource json(InputStream inputStream, Resource parentResource, String childName) {
+        return json(inputStream, parentResource.getPath() + "/" + childName);
+    }
+
+    /**
+     * Import content of JSON file into repository. Auto-creates parent
+     * hierarchies as nt:unstrucured nodes if missing.
+     * @param inputStream Input stream with JSON content
+     * @param destPath Path to import the JSON content to
+     * @return Resource
+     */
+    public Resource json(InputStream inputStream, String destPath) {
+        try {
+            String parentPath = ResourceUtil.getParent(destPath);
+            String childName = ResourceUtil.getName(destPath);
+
+            Resource parentResource = resourceResolver.getResource(parentPath);
+            if (parentResource == null) {
+                parentResource = createResourceHierarchy(parentPath);
+            }
+            if (parentResource.getChild(childName) != null) {
+                throw new IllegalArgumentException("Resource does already exist: " + destPath);
+            }
+
+            String jsonString = convertToJsonString(inputStream).trim();
+            JSONObject json = new JSONObject(jsonString);
+            return this.createResource(parentResource, childName, json);
+        } catch (JSONException ex) {
+            throw new RuntimeException(ex);
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private Resource createResourceHierarchy(String path) {
+        String parentPath = ResourceUtil.getParent(path);
+        if (parentPath == null) {
+            return null;
+        }
+        Resource parentResource = resourceResolver.getResource(parentPath);
+        if (parentResource == null) {
+            parentResource = createResourceHierarchy(parentPath);
+        }
+        Map<String, Object> props = new HashMap<String, Object>();
+        props.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+        try {
+            return resourceResolver.create(parentResource, ResourceUtil.getName(path), props);
+        } catch (PersistenceException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private Resource createResource(Resource parentResource, String childName, JSONObject jsonObject)
+            throws IOException, JSONException {
+
+        // collect all properties first
+        Map<String, Object> props = new HashMap<String, Object>();
+        JSONArray names = jsonObject.names();
+        for (int i = 0; names != null && i < names.length(); i++) {
+            final String name = names.getString(i);
+            if (!IGNORED_NAMES.contains(name)) {
+                Object obj = jsonObject.get(name);
+                if (!(obj instanceof JSONObject)) {
+                    this.setProperty(props, name, obj);
+                }
+            }
+        }
+
+        // validate JCR primary type
+        Object primaryTypeObj = jsonObject.opt(JcrConstants.JCR_PRIMARYTYPE);
+        String primaryType = null;
+        if (primaryTypeObj != null) {
+            primaryType = String.valueOf(primaryTypeObj);
+        }
+        if (primaryType == null) {
+            primaryType = JcrConstants.NT_UNSTRUCTURED;
+        }
+        props.put(JcrConstants.JCR_PRIMARYTYPE, primaryType);
+
+        // create resource
+        Resource resource = resourceResolver.create(parentResource, childName, props);
+
+        // add child resources
+        for (int i = 0; names != null && i < names.length(); i++) {
+            final String name = names.getString(i);
+            if (!IGNORED_NAMES.contains(name)) {
+                Object obj = jsonObject.get(name);
+                if (obj instanceof JSONObject) {
+                    createResource(resource, name, (JSONObject) obj);
+                }
+            }
+        }
+
+        return resource;
+    }
+
+    private void setProperty(Map<String, Object> props, String name, Object value) throws JSONException {
+        if (value instanceof JSONArray) {
+            // multivalue
+            final JSONArray array = (JSONArray) value;
+            if (array.length() > 0) {
+                final Object[] values = new Object[array.length()];
+                for (int i = 0; i < array.length(); i++) {
+                    values[i] = array.get(i);
+                }
+
+                if (values[0] instanceof Double || values[0] instanceof Float) {
+                    Double[] arrayValues = new Double[values.length];
+                    for (int i = 0; i < values.length; i++) {
+                        arrayValues[i] = (Double) values[i];
+                    }
+                    props.put(cleanupJsonName(name), arrayValues);
+                } else if (values[0] instanceof Number) {
+                    Long[] arrayValues = new Long[values.length];
+                    for (int i = 0; i < values.length; i++) {
+                        arrayValues[i] = ((Number) values[i]).longValue();
+                    }
+                    props.put(cleanupJsonName(name), arrayValues);
+                } else if (values[0] instanceof Boolean) {
+                    Boolean[] arrayValues = new Boolean[values.length];
+                    for (int i = 0; i < values.length; i++) {
+                        arrayValues[i] = (Boolean) values[i];
+                    }
+                    props.put(cleanupJsonName(name), arrayValues);
+                } else {
+                    String[] arrayValues = new String[values.length];
+                    for (int i = 0; i < values.length; i++) {
+                        arrayValues[i] = values[i].toString();
+                    }
+                    props.put(cleanupJsonName(name), arrayValues);
+                }
+            } else {
+                props.put(cleanupJsonName(name), new String[0]);
+            }
+
+        } else {
+            // single value
+            if (value instanceof Double || value instanceof Float) {
+                props.put(cleanupJsonName(name), value);
+            } else if (value instanceof Number) {
+                props.put(cleanupJsonName(name), ((Number) value).longValue());
+            } else if (value instanceof Boolean) {
+                props.put(cleanupJsonName(name), value);
+            } else {
+                String stringValue = value.toString();
+
+                // check if value is a Calendar object
+                Calendar calendar = tryParseCalendarValue(stringValue);
+                if (calendar != null) {
+                    props.put(cleanupJsonName(name), calendar);
+                } else {
+                    props.put(cleanupJsonName(name), stringValue);
+                }
+
+            }
+        }
+    }
+
+    private String cleanupJsonName(String name) {
+        if (name.startsWith(REFERENCE)) {
+            return name.substring(REFERENCE.length());
+        }
+        if (name.startsWith(PATH)) {
+            return name.substring(PATH.length());
+        }
+        return name;
+    }
+
+    private String convertToJsonString(InputStream inputStream) {
+        try {
+            return IOUtils.toString(inputStream);
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        } finally {
+            try {
+                inputStream.close();
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+    }
+
+    private Calendar tryParseCalendarValue(String value) {
+        if (StringUtils.isNotBlank(value)) {
+            synchronized (calendarFormat) {
+                try {
+                    Date date = calendarFormat.parse(value);
+                    Calendar calendar = Calendar.getInstance();
+                    calendar.setTime(date);
+                    return calendar;
+                } catch (ParseException ex) {
+                    // ignore
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Import binary file as nt:file binary node into repository. Auto-creates
+     * parent hierarchies as nt:unstrucured nodes if missing. Mime type is
+     * auto-detected from resource name.
+     * @param classpathResource Classpath resource URL for binary file.
+     * @param path Path to mount binary data to (parent nodes created
+     *            automatically)
+     * @return Resource with binary data
+     */
+    public Resource binaryFile(String classpathResource, String path) {
+        InputStream is = ContentLoader.class.getResourceAsStream(classpathResource);
+        if (is == null) {
+            throw new IllegalArgumentException("Classpath resource not found: " + classpathResource);
+        }
+        try {
+            return binaryFile(is, path, detectMimeTypeFromName(path));
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Import binary file as nt:file binary node into repository. Auto-creates
+     * parent hierarchies as nt:unstrucured nodes if missing.
+     * @param classpathResource Classpath resource URL for binary file.
+     * @param path Path to mount binary data to (parent nodes created
+     *            automatically)
+     * @param mimeType Mime type of binary data
+     * @return Resource with binary data
+     */
+    public Resource binaryFile(String classpathResource, String path, String mimeType) {
+        InputStream is = ContentLoader.class.getResourceAsStream(classpathResource);
+        if (is == null) {
+            throw new IllegalArgumentException("Classpath resource not found: " + classpathResource);
+        }
+        try {
+            return binaryFile(is, path, mimeType);
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Import binary file as nt:file binary node into repository. Auto-creates
+     * parent hierarchies as nt:unstrucured nodes if missing. Mime type is
+     * auto-detected from resource name.
+     * @param inputStream Input stream for binary data
+     * @param path Path to mount binary data to (parent nodes created
+     *            automatically)
+     * @return Resource with binary data
+     */
+    public Resource binaryFile(InputStream inputStream, String path) {
+        return binaryFile(inputStream, path, detectMimeTypeFromName(path));
+    }
+
+    /**
+     * Import binary file as nt:file binary node into repository. Auto-creates
+     * parent hierarchies as nt:unstrucured nodes if missing.
+     * @param inputStream Input stream for binary data
+     * @param path Path to mount binary data to (parent nodes created
+     *            automatically)
+     * @param mimeType Mime type of binary data
+     * @return Resource with binary data
+     */
+    public Resource binaryFile(InputStream inputStream, String path, String mimeType) {
+        String parentPath = ResourceUtil.getParent(path, 1);
+        String name = ResourceUtil.getName(path);
+        Resource parentResource = resourceResolver.getResource(parentPath);
+        if (parentResource == null) {
+            parentResource = createResourceHierarchy(parentPath);
+        }
+        return binaryFile(inputStream, parentResource, name, mimeType);
+    }
+
+    /**
+     * Import binary file as nt:file binary node into repository. Auto-creates
+     * parent hierarchies as nt:unstrucured nodes if missing. Mime type is
+     * auto-detected from resource name.
+     * @param inputStream Input stream for binary data
+     * @param parentResource Parent resource
+     * @param name Resource name for nt:file
+     * @return Resource with binary data
+     */
+    public Resource binaryFile(InputStream inputStream, Resource parentResource, String name) {
+        return binaryFile(inputStream, parentResource, name, detectMimeTypeFromName(name));
+    }
+
+    /**
+     * Import binary file as nt:file binary node into repository. Auto-creates
+     * parent hierarchies as nt:unstrucured nodes if missing.
+     * @param inputStream Input stream for binary data
+     * @param parentResource Parent resource
+     * @param name Resource name for nt:file
+     * @param mimeType Mime type of binary data
+     * @return Resource with binary data
+     */
+    public Resource binaryFile(InputStream inputStream, Resource parentResource, String name, String mimeType) {
+        try {
+            Resource file = resourceResolver.create(parentResource, name,
+                    ImmutableMap.<String, Object> builder().put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_FILE)
+                            .build());
+            resourceResolver.create(file, JcrConstants.JCR_CONTENT,
+                    ImmutableMap.<String, Object> builder().put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_RESOURCE)
+                            .put(JcrConstants.JCR_DATA, inputStream).put(JcrConstants.JCR_MIMETYPE, mimeType).build());
+            return file;
+        } catch (PersistenceException ex) {
+            throw new RuntimeException("Unable to create resource at " + parentResource.getPath() + "/" + name, ex);
+        }
+    }
+
+    /**
+     * Import binary file as nt:resource binary node into repository.
+     * Auto-creates parent hierarchies as nt:unstrucured nodes if missing. Mime
+     * type is auto-detected from resource name.
+     * @param classpathResource Classpath resource URL for binary file.
+     * @param path Path to mount binary data to (parent nodes created
+     *            automatically)
+     * @return Resource with binary data
+     */
+    public Resource binaryResource(String classpathResource, String path) {
+        InputStream is = ContentLoader.class.getResourceAsStream(classpathResource);
+        if (is == null) {
+            throw new IllegalArgumentException("Classpath resource not found: " + classpathResource);
+        }
+        try {
+            return binaryResource(is, path, detectMimeTypeFromName(path));
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Import binary file as nt:resource binary node into repository.
+     * Auto-creates parent hierarchies as nt:unstrucured nodes if missing.
+     * @param classpathResource Classpath resource URL for binary file.
+     * @param path Path to mount binary data to (parent nodes created
+     *            automatically)
+     * @param mimeType Mime type of binary data
+     * @return Resource with binary data
+     */
+    public Resource binaryResource(String classpathResource, String path, String mimeType) {
+        InputStream is = ContentLoader.class.getResourceAsStream(classpathResource);
+        if (is == null) {
+            throw new IllegalArgumentException("Classpath resource not found: " + classpathResource);
+        }
+        try {
+            return binaryResource(is, path, mimeType);
+        } finally {
+            try {
+                is.close();
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Import binary file as nt:resource binary node into repository.
+     * Auto-creates parent hierarchies as nt:unstrucured nodes if missing. Mime
+     * type is auto-detected from resource name.
+     * @param inputStream Input stream for binary data
+     * @param path Path to mount binary data to (parent nodes created
+     *            automatically)
+     * @return Resource with binary data
+     */
+    public Resource binaryResource(InputStream inputStream, String path) {
+        return binaryResource(inputStream, path, detectMimeTypeFromName(path));
+    }
+
+    /**
+     * Import binary file as nt:resource binary node into repository.
+     * Auto-creates parent hierarchies as nt:unstrucured nodes if missing.
+     * @param inputStream Input stream for binary data
+     * @param path Path to mount binary data to (parent nodes created
+     *            automatically)
+     * @param mimeType Mime type of binary data
+     * @return Resource with binary data
+     */
+    public Resource binaryResource(InputStream inputStream, String path, String mimeType) {
+        String parentPath = ResourceUtil.getParent(path, 1);
+        String name = ResourceUtil.getName(path);
+        Resource parentResource = resourceResolver.getResource(parentPath);
+        if (parentResource == null) {
+            parentResource = createResourceHierarchy(parentPath);
+        }
+        return binaryResource(inputStream, parentResource, name, mimeType);
+    }
+
+    /**
+     * Import binary file as nt:resource binary node into repository.
+     * Auto-creates parent hierarchies as nt:unstrucured nodes if missing. Mime
+     * type is auto-detected from resource name.
+     * @param inputStream Input stream for binary data
+     * @param parentResource Parent resource
+     * @param name Resource name for nt:resource
+     * @return Resource with binary data
+     */
+    public Resource binaryResource(InputStream inputStream, Resource parentResource, String name) {
+        return binaryResource(inputStream, parentResource, name, detectMimeTypeFromName(name));
+    }
+
+    /**
+     * Import binary file as nt:resource binary node into repository.
+     * Auto-creates parent hierarchies as nt:unstrucured nodes if missing.
+     * @param inputStream Input stream for binary data
+     * @param parentResource Parent resource
+     * @param name Resource name for nt:resource
+     * @param mimeType Mime type of binary data
+     * @return Resource with binary data
+     */
+    public Resource binaryResource(InputStream inputStream, Resource parentResource, String name, String mimeType) {
+        try {
+            return resourceResolver.create(parentResource, name,
+                    ImmutableMap.<String, Object> builder().put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_RESOURCE)
+                            .put(JcrConstants.JCR_DATA, inputStream).put(JcrConstants.JCR_MIMETYPE, mimeType).build());
+        } catch (PersistenceException ex) {
+            throw new RuntimeException("Unable to create resource at " + parentResource.getPath() + "/" + name, ex);
+        }
+    }
+
+    /**
+     * Detected mime type from name (file extension) using Mime Type service.
+     * Fallback to application/octet-stream.
+     * @param name Node name
+     * @return Mime type (never null)
+     */
+    private String detectMimeTypeFromName(String name) {
+        String mimeType = null;
+        String fileExtension = StringUtils.substringAfterLast(name, ".");
+        if (bundleContext != null && StringUtils.isNotEmpty(fileExtension)) {
+            ServiceReference ref = bundleContext.getServiceReference(MimeTypeService.class.getName());
+            if (ref != null) {
+                MimeTypeService mimeTypeService = (MimeTypeService) bundleContext.getService(ref);
+                mimeType = mimeTypeService.getMimeType(fileExtension);
+            }
+        }
+        return StringUtils.defaultString(mimeType, CONTENTTYPE_OCTET_STREAM);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/loader/package-info.java b/src/main/java/org/apache/sling/testing/mock/sling/loader/package-info.java
new file mode 100644
index 0000000..ebe7af5
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/loader/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.
+ */
+/**
+ * Helpers for importing test content into the mocked repositories / resource hierarchies.
+ */
+package org.apache.sling.testing.mock.sling.loader;
+
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/package-info.java b/src/main/java/org/apache/sling/testing/mock/sling/package-info.java
new file mode 100644
index 0000000..5919826
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/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 Sling APIs.
+ */
+package org.apache.sling.testing.mock.sling;
+
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/services/MockMimeTypeService.java b/src/main/java/org/apache/sling/testing/mock/sling/services/MockMimeTypeService.java
new file mode 100644
index 0000000..fbc6fb7
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/services/MockMimeTypeService.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling.services;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.mime.MimeTypeService;
+import org.apache.sling.commons.mime.internal.MimeTypeServiceImpl;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.osgi.service.component.ComponentContext;
+
+/**
+ * Mock {@link MimeTypeService} implementation.
+ */
+@Component(inherit = false)
+@Service(MimeTypeService.class)
+public final class MockMimeTypeService extends MimeTypeServiceImpl {
+
+    private boolean initialized;
+
+    /**
+     * Do lazy initializing because reading to avoid reading all defined mime
+     * types from disk if not required
+     */
+    private void lazyInitialization() {
+        if (!this.initialized) {
+            this.initialized = true;
+            // activate service in simulated OSGi environment
+            ComponentContext componentContext = MockOsgi.newComponentContext();
+            this.bindLogService(MockOsgi.newLogService(getClass()));
+            activate(componentContext);
+        }
+    }
+
+    @Override
+    public String getMimeType(final String name) {
+        lazyInitialization();
+        return super.getMimeType(name);
+    }
+
+    @Override
+    public String getExtension(final String mimeType) {
+        lazyInitialization();
+        return super.getExtension(mimeType);
+    }
+
+    @Override
+    public void registerMimeType(final String mimeType, final String... extensions) {
+        lazyInitialization();
+        super.registerMimeType(mimeType, extensions);
+    }
+
+    @Override
+    public void registerMimeType(final InputStream mimeTabStream) throws IOException {
+        lazyInitialization();
+        super.registerMimeType(mimeTabStream);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/services/MockModelAdapterFactory.java b/src/main/java/org/apache/sling/testing/mock/sling/services/MockModelAdapterFactory.java
new file mode 100644
index 0000000..c116b18
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/services/MockModelAdapterFactory.java
@@ -0,0 +1,295 @@
+/*
+ * 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.testing.mock.sling.services;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.adapter.AdapterFactory;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.impl.ModelAdapterFactory;
+import org.apache.sling.models.spi.ImplementationPicker;
+import org.apache.sling.models.spi.Injector;
+import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+import org.osgi.service.component.ComponentContext;
+import org.reflections.Reflections;
+
+/**
+ * Mock {@link ModelAdapterFactory} implementation.
+ */
+@Component(inherit = false)
+@Service(AdapterFactory.class)
+public final class MockModelAdapterFactory extends ModelAdapterFactory {
+
+    private final BundleContext bundleContext;
+
+    /**
+     * @param componentContext OSGi component context
+     */
+    public MockModelAdapterFactory(ComponentContext componentContext) {
+        bundleContext = componentContext.getBundleContext();
+
+        // register service listener to collect injectors
+        // this allows detecting injectors even if they are registered after
+        // this bundle
+        // (which is otherwise currently not supported in the osgi mock
+        // environment)
+        bundleContext.addServiceListener(new InjectorServiceListener());
+
+        // activate service in simulated OSGi environment
+        activate(componentContext);
+    }
+
+    /**
+     * Constructor with default component context
+     */
+    public MockModelAdapterFactory() {
+        this(MockOsgi.newComponentContext());
+    }
+
+    private class InjectorServiceListener implements ServiceListener {
+
+        @Override
+        public void serviceChanged(ServiceEvent event) {
+            Object service = bundleContext.getService(event.getServiceReference());
+            if (service instanceof Injector) {
+                if (event.getType() == ServiceEvent.REGISTERED) {
+                    bindInjector((Injector) service, getServiceProperties(event.getServiceReference()));
+                } else if (event.getType() == ServiceEvent.UNREGISTERING) {
+                    unbindInjector((Injector) service, getServiceProperties(event.getServiceReference()));
+                }
+            }
+            if (service instanceof InjectAnnotationProcessorFactory) {
+                if (event.getType() == ServiceEvent.REGISTERED) {
+                    bindInjectAnnotationProcessorFactory((InjectAnnotationProcessorFactory) service,
+                            getServiceProperties(event.getServiceReference()));
+                } else if (event.getType() == ServiceEvent.UNREGISTERING) {
+                    unbindInjectAnnotationProcessorFactory((InjectAnnotationProcessorFactory) service,
+                            getServiceProperties(event.getServiceReference()));
+                }
+            }
+            if (service instanceof ImplementationPicker) {
+                if (event.getType() == ServiceEvent.REGISTERED) {
+                    bindImplementationPicker((ImplementationPicker) service,
+                            getServiceProperties(event.getServiceReference()));
+                } else if (event.getType() == ServiceEvent.UNREGISTERING) {
+                    unbindImplementationPicker((ImplementationPicker) service,
+                            getServiceProperties(event.getServiceReference()));
+                }
+            }
+        }
+
+        private Map<String, Object> getServiceProperties(ServiceReference reference) {
+            Map<String, Object> props = new HashMap<String, Object>();
+            String[] propertyKeys = reference.getPropertyKeys();
+            for (String key : propertyKeys) {
+                props.put(key, reference.getProperty(key));
+            }
+            return props;
+        }
+
+    }
+
+    /**
+     * Scan classpaths for given package name (and sub packages) to scan for and
+     * register all classes with @Model annotation.
+     * @param packageName Java package name
+     */
+    public void addModelsForPackage(String packageName) {
+        Bundle bundle = new ModelsPackageBundle(packageName, Bundle.ACTIVE);
+        BundleEvent event = new BundleEvent(BundleEvent.STARTED, bundle);
+        MockOsgi.sendBundleEvent(this.bundleContext, event);
+    }
+
+    @SuppressWarnings("unused")
+    private class ModelsPackageBundle implements Bundle {
+
+        private final String packageName;
+        private final int state;
+
+        public ModelsPackageBundle(String packageName, int state) {
+            this.packageName = packageName;
+            this.state = state;
+        }
+
+        @Override
+        public int getState() {
+            return this.state;
+        }
+
+        @Override
+        public Dictionary getHeaders() {
+            Dictionary<String, Object> headers = new Hashtable<String, Object>();
+            headers.put("Sling-Model-Packages", this.packageName);
+            return headers;
+        }
+
+        @Override
+        public Enumeration findEntries(String path, String filePattern, boolean recurse) {
+            Reflections reflections = new Reflections(this.packageName);
+            Set<Class<?>> types = reflections.getTypesAnnotatedWith(Model.class);
+            Vector<URL> urls = new Vector<URL>(); // NOPMD
+            try {
+                for (Class<?> type : types) {
+                    urls.add(new URL("file:/" + type.getName().replace('.', '/') + ".class"));
+                }
+            } catch (MalformedURLException ex) {
+                throw new RuntimeException("Malformed URL.", ex);
+            }
+            return urls.elements();
+        }
+
+        @Override
+        public Class loadClass(String name) throws ClassNotFoundException {
+            return getClass().getClassLoader().loadClass(name);
+        }
+
+        @Override
+        public BundleContext getBundleContext() {
+            return bundleContext;
+        }
+
+        @Override
+        public void start(int options) throws BundleException {
+            // do nothing
+        }
+
+        @Override
+        public void start() throws BundleException {
+            // do nothing
+        }
+
+        @Override
+        public void stop(int options) throws BundleException {
+            // do nothing
+        }
+
+        @Override
+        public void stop() throws BundleException {
+            // do nothing
+        }
+
+        @Override
+        public void update(InputStream input) throws BundleException {
+            // do nothing
+        }
+
+        @Override
+        public void update() throws BundleException {
+            // do nothing
+        }
+
+        @Override
+        public void uninstall() throws BundleException {
+            // do nothing
+        }
+
+        @Override
+        public long getBundleId() {
+            return 0;
+        }
+
+        @Override
+        public String getLocation() {
+            return null;
+        }
+
+        @Override
+        public ServiceReference[] getRegisteredServices() { // NOPMD
+            return null;
+        }
+
+        @Override
+        public ServiceReference[] getServicesInUse() { // NOPMD
+            return null;
+        }
+
+        @Override
+        public boolean hasPermission(Object permission) {
+            return false;
+        }
+
+        @Override
+        public URL getResource(String name) {
+            return null;
+        }
+
+        @Override
+        public Dictionary getHeaders(String locale) {
+            return null;
+        }
+
+        @Override
+        public String getSymbolicName() {
+            return null;
+        }
+
+        @Override
+        public Enumeration getResources(String name) throws IOException {
+            return null;
+        }
+
+        @Override
+        public Enumeration getEntryPaths(String path) {
+            return null;
+        }
+
+        @Override
+        public URL getEntry(String path) {
+            return null;
+        }
+
+        @Override
+        public long getLastModified() {
+            return 0;
+        }
+
+        // this is part of org.osgi 4.2.0
+        public Map getSignerCertificates(int signersType) {
+            return null;
+        }
+
+        // this is part of org.osgi 4.2.0
+        public Version getVersion() {
+            return null;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/services/MockSlingSettingService.java b/src/main/java/org/apache/sling/testing/mock/sling/services/MockSlingSettingService.java
new file mode 100644
index 0000000..671b812
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/services/MockSlingSettingService.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling.services;
+
+import java.net.URL;
+import java.util.Set;
+
+import org.apache.sling.settings.SlingSettingsService;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Mock implementation of {@link SlingSettingsService}.
+ */
+public final class MockSlingSettingService implements SlingSettingsService {
+
+    private Set<String> runModes;
+
+    /**
+     * Instantiate with no default run modes.
+     */
+    public MockSlingSettingService() {
+        this(ImmutableSet.<String> of());
+    }
+
+    /**
+     * Instantiate with given run modes
+     * @param defaultRunModes Run modes
+     */
+    public MockSlingSettingService(Set<String> defaultRunModes) {
+        this.runModes = defaultRunModes;
+    }
+
+    @Override
+    public Set<String> getRunModes() {
+        return ImmutableSet.copyOf(this.runModes);
+    }
+
+    public void setRunModes(Set<String> runModes) {
+        this.runModes = runModes;
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public String getAbsolutePathWithinSlingHome(String relativePath) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getSlingId() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getSlingHomePath() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public URL getSlingHome() {
+        throw new UnsupportedOperationException();
+    }
+
+    // part of Sling API 2.7
+    public String getSlingName() {
+        throw new UnsupportedOperationException();
+    }
+
+    // part of Sling API 2.7
+    public String getSlingDescription() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/servlet/CookieSupport.java b/src/main/java/org/apache/sling/testing/mock/sling/servlet/CookieSupport.java
new file mode 100644
index 0000000..ef7c171
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/servlet/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.testing.mock.sling.servlet;
+
+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/testing/mock/sling/servlet/HeaderSupport.java b/src/main/java/org/apache/sling/testing/mock/sling/servlet/HeaderSupport.java
new file mode 100644
index 0000000..cc91c93
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/servlet/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.testing.mock.sling.servlet;
+
+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/testing/mock/sling/servlet/MockHttpSession.java b/src/main/java/org/apache/sling/testing/mock/sling/servlet/MockHttpSession.java
new file mode 100644
index 0000000..f877441
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/servlet/MockHttpSession.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling.servlet;
+
+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;
+
+/**
+ * Mock {@link HttpSession} implementation.
+ */
+public final class MockHttpSession implements HttpSession {
+
+    private final ServletContext servletContext = new MockServletContext();
+    private final Map<String, Object> attributeMap = new HashMap<String, Object>();
+    private final String sessionID = UUID.randomUUID().toString();
+    private final long creationTime = System.currentTimeMillis();
+
+    @Override
+    public ServletContext getServletContext() {
+        return this.servletContext;
+    }
+
+    @Override
+    public Object getAttribute(final String name) {
+        return this.attributeMap.get(name);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Enumeration<String> getAttributeNames() {
+        return IteratorUtils.asEnumeration(this.attributeMap.keySet().iterator());
+    }
+
+    @Override
+    public String getId() {
+        return this.sessionID;
+    }
+
+    @Override
+    public long getCreationTime() {
+        return this.creationTime;
+    }
+
+    @Override
+    public Object getValue(final String name) {
+        return getAttribute(name);
+    }
+
+    @Override
+    public String[] getValueNames() {
+        return this.attributeMap.keySet().toArray(new String[this.attributeMap.keySet().size()]);
+    }
+
+    @Override
+    public void putValue(final String name, final Object value) {
+        setAttribute(name, value);
+    }
+
+    @Override
+    public void removeAttribute(final String name) {
+        this.attributeMap.remove(name);
+    }
+
+    @Override
+    public void removeValue(final String name) {
+        this.attributeMap.remove(name);
+    }
+
+    @Override
+    public void setAttribute(final String name, final Object value) {
+        this.attributeMap.put(name, value);
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public long getLastAccessedTime() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getMaxInactiveInterval() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public javax.servlet.http.HttpSessionContext getSessionContext() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void invalidate() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isNew() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setMaxInactiveInterval(final int interval) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/servlet/MockRequestPathInfo.java b/src/main/java/org/apache/sling/testing/mock/sling/servlet/MockRequestPathInfo.java
new file mode 100644
index 0000000..6e058e4
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/servlet/MockRequestPathInfo.java
@@ -0,0 +1,86 @@
+/*
+ * 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.testing.mock.sling.servlet;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Mock {@link RequestPathInfo} implementation.
+ */
+public final 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/testing/mock/sling/servlet/MockServletContext.java b/src/main/java/org/apache/sling/testing/mock/sling/servlet/MockServletContext.java
new file mode 100644
index 0000000..107c22f
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/servlet/MockServletContext.java
@@ -0,0 +1,301 @@
+/*
+ * 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.testing.mock.sling.servlet;
+
+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;
+
+/**
+ * Mock {@link ServletContext} implementation.
+ */
+public final 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/testing/mock/sling/servlet/MockSlingHttpServletRequest.java b/src/main/java/org/apache/sling/testing/mock/sling/servlet/MockSlingHttpServletRequest.java
new file mode 100644
index 0000000..4444945
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/servlet/MockSlingHttpServletRequest.java
@@ -0,0 +1,696 @@
+/*
+ * 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.testing.mock.sling.servlet;
+
+import java.io.BufferedReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+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 org.apache.sling.testing.mock.sling.MockSling;
+
+/**
+ * Mock {@link SlingHttpServletRequest} implementation.
+ */
+public class MockSlingHttpServletRequest extends SlingAdaptable implements SlingHttpServletRequest {
+
+    private ResourceResolver resourceResolver;
+    private RequestPathInfo requestPathInfo = new MockRequestPathInfo();
+    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();
+
+    /**
+     * Instantiate with default resource resolver
+     */
+    public MockSlingHttpServletRequest() {
+        this.resourceResolver = MockSling.newResourceResolver();
+    }
+
+    /**
+     * @param resourceResolver Resource resolver
+     */
+    public MockSlingHttpServletRequest(ResourceResolver resourceResolver) {
+        this.resourceResolver = resourceResolver;
+    }
+
+    @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 = new MockHttpSession();
+        }
+        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);
+    }
+
+    // --- unsupported operations ---
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(Resource dispatcherResource) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String dispatcherPath, RequestDispatcherOptions options) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(Resource dispatcherResource, RequestDispatcherOptions options) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestParameter getRequestParameter(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestParameterMap getRequestParameterMap() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestParameter[] getRequestParameters(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestProgressTracker getRequestProgressTracker() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ResourceBundle getResourceBundle(Locale pLocale) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ResourceBundle getResourceBundle(String baseName, Locale locale) {
+        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 getRemoteUser() {
+        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 getCharacterEncoding() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getContentLength() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getContentType() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServletInputStream getInputStream() {
+        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 String getRemoteAddr() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getRemoteHost() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getRemotePort() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public RequestDispatcher getRequestDispatcher(String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setCharacterEncoding(String env) {
+        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();
+    }
+
+    // part of Sling API 2.7
+    public List<RequestParameter> getRequestParameterList() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/servlet/MockSlingHttpServletResponse.java b/src/main/java/org/apache/sling/testing/mock/sling/servlet/MockSlingHttpServletResponse.java
new file mode 100644
index 0000000..b5d918e
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/servlet/MockSlingHttpServletResponse.java
@@ -0,0 +1,280 @@
+/*
+ * 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.testing.mock.sling.servlet;
+
+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.CharEncoding;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.adapter.SlingAdaptable;
+
+/**
+ * Mock {@link SlingHttpServletResponse} implementation.
+ */
+public class MockSlingHttpServletResponse extends SlingAdaptable implements SlingHttpServletResponse {
+
+    private static final String CHARSET_SEPARATOR = ";charset=";
+
+    private String contentType;
+    private String characterEncoding = CharEncoding.ISO_8859_1;
+    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/testing/mock/sling/servlet/ResponseBodySupport.java b/src/main/java/org/apache/sling/testing/mock/sling/servlet/ResponseBodySupport.java
new file mode 100644
index 0000000..128299b
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/servlet/ResponseBodySupport.java
@@ -0,0 +1,93 @@
+/*
+ * 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.testing.mock.sling.servlet;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+
+import javax.servlet.ServletOutputStream;
+
+/**
+ * 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(), charset));
+            } catch (UnsupportedEncodingException ex) {
+                throw new RuntimeException("Unsupported encoding: " + 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(), charset);
+        } catch (UnsupportedEncodingException ex) {
+            throw new RuntimeException("Unsupported encoding: " + charset, ex);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/servlet/package-info.java b/src/main/java/org/apache/sling/testing/mock/sling/servlet/package-info.java
new file mode 100644
index 0000000..389e6ec
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/servlet/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.
+ */
+package org.apache.sling.testing.mock.sling.servlet;
+
diff --git a/src/main/java/org/apache/sling/testing/mock/sling/spi/ResourceResolverTypeAdapter.java b/src/main/java/org/apache/sling/testing/mock/sling/spi/ResourceResolverTypeAdapter.java
new file mode 100644
index 0000000..cc20279
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/sling/spi/ResourceResolverTypeAdapter.java
@@ -0,0 +1,46 @@
+/*
+ * 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.testing.mock.sling.spi;
+
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.jcr.api.SlingRepository;
+
+/**
+ * SPI interface for resource resolver type implementations to provide a mock
+ * resource resolver factory.
+ */
+public interface ResourceResolverTypeAdapter {
+
+    /**
+     * Gets resource resolver factory instance. Can be null if only a
+     * SlingRepository is provided, in this case the method
+     * {@link #newSlingRepository()} has to return a value.
+     * @return Resource resolver factory instance or null
+     */
+    ResourceResolverFactory newResourceResolverFactory();
+
+    /**
+     * Get SlingRepository instance. Can be null if a resource resolver factory
+     * is provided, in this case the method
+     * {@link #newResourceResolverFactory()} has to return a value.
+     * @return Sling repository instance or null
+     */
+    SlingRepository newSlingRepository();
+
+}
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
new file mode 100644
index 0000000..407ab8b
--- /dev/null
+++ b/src/site/markdown/index.md
@@ -0,0 +1,67 @@
+## About Sling Mocks
+
+Mock implementation of selected Sling APIs.
+
+
+### Maven Dependency
+
+```xml
+<dependency>
+  <groupId>org.apache.sling</groupId>
+  <artifactId>org.apache.sling.testing.sling-mock</artifactId>
+  <version>1.0.0-SNAPHOT</version>
+</dependency>
+```
+
+### Documentation
+
+* [Sling Mocks Usage][usage-mocks]
+* [Content Loader Usage][usage-content-loader]
+* [API Documentation][apidocs]
+* [Changelog][changelog]
+
+
+### Implemented mock features
+
+The mock implementation supports:
+
+* `ResourceResolver` implementation for reading and writing resource data using the Sling Resource API
+    * Backed by a [mocked][jcr-mock] or real Jackrabbit JCR implementation
+    * Uses the productive [Sling JCR resource provider implementation][jcr-resource] internally to do the Resource-JCR mapping
+    * Alternatively the non-JCR mock implementation provided by the 
+   [Sling resourceresolver-mock implementation][resourceresolver-mock] can be used
+* `AdpaterManager` implementation for registering adapter factories and resolving adaptions
+    * The implementation is thread-safe so it can be used in parallel running unit tests
+* `SlingScriptHelper` implementation providing access to mocked request/response objects and supports getting
+   OSGi services from the [mocked OSGi][osgi-mock] environment.
+* Implementations of the servlet-related Sling API classes like `SlingHttpServletRequest` and `SlingHttpServletRequest`
+    * It is possible to set request data to simulate a certian Sling HTTP request
+* Additional services like `MockModelAdapterFactory` and  `MimeTypeService` 
+
+[osgi-mock]: http://sling.apache.org/testing/osgi-mock/
+[jcr-mock]: http://sling.apache.org/testing/jcr-mock/
+[jcr-resource]: http://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/resource
+[resourceresolver-mock]: http://svn.eu.apache.org/repos/asf/sling/trunk/testing/resourceresolver-moc
+
+The following features are *not supported*:
+
+* It is not possible (nor intended) to really execute sling components/scripts and render their results.
+    * The goal is to test supporting classes in Sling context, not the sling components/scripts themselves
+
+See [Sling Mocks Usage][usage-mocks].
+
+
+### Additional features
+
+Additional features provided:
+
+* `ContentLoader` supports importing JSON data and binary data into the mock resource hierarchy to easily 
+  prepare a test fixture consisting of a hierarchy of resources and properties.
+    * The same JSON format can be used that is provided by the Sling GET servlet for output
+
+See [Content Loader Usage][usage-content-loader].
+
+[usage-mocks]: usage-mocks.html
+[usage-content-loader]: usage-content-loader.html
+[apidocs]: apidocs/
+[changelog]: changes-report.html
diff --git a/src/site/markdown/resource-resolver-types.md b/src/site/markdown/resource-resolver-types.md
new file mode 100644
index 0000000..3cacb49
--- /dev/null
+++ b/src/site/markdown/resource-resolver-types.md
@@ -0,0 +1,53 @@
+## Resource Resolver Types
+
+The Sling Mocks resource resolver implementation supports different "types" of adapters for the mocks.
+Depending on the type an underlying JCR repository is used or not, and the data ist stored in-memory or in a real 
+repository.
+
+This pages lists all resource resolver types currently supported.
+
+### RESOURCERESOLVER_MOCK (default)
+
+* Simulates an In-Memory resource tree, does not provide adaptions to JCR API.
+* Based on the [Sling resourceresolver-mock implementation][resourceresolver-mock] implementation
+* You can use it to make sure the code you want to test does not contain references to JCR API.
+* Behaves slightly different from JCR resource mapping e.g. handling binary and date values.
+* This resource resolver type is very fast because data is stored in memory and no JCR mapping is applied.
+
+### JCR_MOCK
+
+* Based on the [JCR Mocks][jcr-mock] implementation
+* Uses the productive [Sling JCR resource provider implementation][jcr-resource] internally to do the Resource-JCR mapping
+* Is quite fast because data is stored only in-memory
+
+### JCR_JACKRABBIT
+
+* Uses a real JCR Jackrabbit implementation (not Oak) as provided by [sling/commons/testing][sling-comons-testing]
+* Full JCR/Sling features supported e.g. observations manager, transactions, versioning
+* Uses the productive [Sling JCR resource provider implementation][jcr-resource] internally to do the Resource-JCR mapping
+* Takes some seconds for startup on the first access 
+* All node types that are used when reading/writing data have to be registered
+
+_Warnings/Remarks_
+
+* The repository is not cleared for each unit test, so make sure us use a unique node path for each unit test.
+* To import Sling content you have to fully register all node types required for the data
+* The [sling/commons/testing][sling-comons-testing] dependency introduces a lot of further dependencies from
+  jackrabbit and others, be careful that they do not conflict and are imported in the right order in your test project
+
+To use this type you have to declare an additional dependency in your test project:
+
+```xml
+<dependency>
+  <groupId>org.apache.sling</groupId>
+  <artifactId>org.apache.sling.testing.sling-mock-jackrabbit</artifactId>
+  <version>1.0.0-SNAPHOT</version>
+  <scope>test</scope>
+</dependency>
+```
+
+
+[jcr-mock]: http://sling.apache.org/testing/jcr-mock/
+[jcr-resource]: http://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/resource
+[resourceresolver-mock]: http://svn.eu.apache.org/repos/asf/sling/trunk/testing/resourceresolver-moc
+[sling-comons-testing]: http://svn.apache.org/repos/asf/sling/trunk/bundles/commons/testing
diff --git a/src/site/markdown/usage-content-loader.md b/src/site/markdown/usage-content-loader.md
new file mode 100644
index 0000000..8d88146
--- /dev/null
+++ b/src/site/markdown/usage-content-loader.md
@@ -0,0 +1,59 @@
+## Usage
+
+### Import resource data from JSON file in classpath
+
+With the `ContentLoader` it is possible to import structured resource and property data from a JSON file stored
+in the classpath beneath the unit tests. This data can be used as text fixture for unit tests.
+
+Example JSON data:
+
+```json
+{
+  "jcr:primaryType": "app:Page",
+  "jcr:content": {
+    "jcr:primaryType": "app:PageContent",
+    "jcr:title": "English",
+    "app:template": "/apps/sample/templates/homepage",
+    "sling:resourceType": "sample/components/homepage",
+    "jcr:createdBy": "admin",
+    "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+    "par": {
+      "jcr:primaryType": "nt:unstructured",
+      "sling:resourceType": "foundation/components/parsys",
+      "colctrl": {
+        "jcr:primaryType": "nt:unstructured",
+        "layout": "2;colctrl-lt0",
+        "sling:resourceType": "foundation/components/parsys/colctrl"
+      }
+    }
+  }
+}
+```
+
+Example code to import the JSON data:
+
+```java
+ResourceResolver resolver = MockSling.newResourceResolver();
+ContentLoader contentLoader = new ContentLoader(resolver);
+contentLoader.json("/sample-data.json", "/content/sample/en");
+```
+
+This codes creates a new resource at `/content/sample/en` (and - if not existent - the parent resources) and
+imports the JSON data to this node. It can be accessed using the Sling Resource or JCR API afterwards.
+
+
+### Import binary data from file in classpath
+
+With the `ContentLoader` it is possible to import a binary file stored in the classpath beneath the unit tests.
+The data is stored usig a nt:file/nt:resource or nt:resource node type. 
+
+Example code to import a binary file:
+
+```java
+ResourceResolver resolver = MockSling.newResourceResolver();
+ContentLoader contentLoader = new ContentLoader(resolver);
+contentLoader.binaryFile("/sample-file.gif", "/content/binary/sample-file.gif");
+```
+
+This codes creates a new resource at `/content/binary/sample-file.gif` (and - if not existent - the parent 
+resources) and imports the binary data to a jcr:content subnode.
diff --git a/src/site/markdown/usage-mocks.md b/src/site/markdown/usage-mocks.md
new file mode 100644
index 0000000..a942c09
--- /dev/null
+++ b/src/site/markdown/usage-mocks.md
@@ -0,0 +1,130 @@
+## Usage
+
+The factory class `MockSling` allows to instantiate the different mock implementations.
+
+### Sling Resource Resolver
+
+Example:
+
+```java
+// get a resource resolver
+ResourceResolver resolver = MockSling.newResourceResolver();
+
+// get a resource resolver backed by a specific repository type
+ResourceResolver resolver = MockSling.newResourceResolver(ResourceResolverType.JCR_MOCK);
+```
+The following types are supported currently: [Resource Resolver Types](resource-resolver-types.html)
+
+### Adapter Factories
+
+You can register your own or existing adapter factories to support adaptions e.g. for classes extending `SlingAdaptable`.
+
+Example:
+
+```java
+// register adapter factory
+BundleContext bundleContext = MockOsgi.newBundleContext();
+MockSling.setAdapterManagerBundleContext(bundleContext);
+bundleContext.registerService(myAdapterFactory);
+
+// test adaption
+MyClass object = resource.adaptTo(MyClass.class);
+
+// cleanup after unit test
+MockSling.clearAdapterManagerBundleContext();
+```
+
+Make sure you clean up the adapter manager bundle association after running the unit test otherwise it can 
+interfere with the following tests. If you use the `SlingContext` Junit rule this is done automatically for you.
+
+
+### SlingScriptHelper
+
+Example:
+
+```java
+// get script helper
+SlingScriptHelper scriptHelper = MockSling.newSlingScriptHelper();
+
+// get request
+SlingHttpServletRequest request = scriptHelper.getRequest();
+
+// get service
+MyService object = scriptHelper.getService(MyService.class);
+```
+
+To support getting OSGi services you have to register them via the `BundleContext` interface of the
+[JCR Mocks][jcr-mock] before. You can use an alternative factory method for the `SlingScriptHelper` providing
+existing instances of request, response and bundle context. 
+
+
+### SlingHttpServletRequest
+
+Example for preparing a sling request with custom request data:
+
+```java
+// prepare sling request
+ResourceResolver resourceResolver = MockSling.newResourceResolver();
+MockSlingHttpServletRequest request = new MockSlingHttpServletRequest(resourceResolver);
+
+// simulate query string
+request.setQueryString("param1=aaa&param2=bbb");
+
+// alternative - set query parameters as map
+request.setParameterMap(ImmutableMap.<String,Object>builder()
+    .put("param1", "aaa")
+    .put("param2", "bbb")
+    .build());
+
+// set current resource
+request.setResource(resourceResolver.getResource("/content/sample"));
+
+// set sling request path info properties
+MockRequestPathInfo requestPathInfo = (MockRequestPathInfo)request.getRequestPathInfo();
+requestPathInfo.setSelectorString("selector1.selector2");
+requestPathInfo.setExtension("html");
+
+// set method
+request.setMethod(HttpConstants.METHOD_POST);
+
+// set attributes
+request.setAttribute("attr1", "value1");
+
+// set headers
+request.addHeader("header1", "value1");
+
+// set cookies
+request.addCookie(new Cookie("cookie1", "value1"));
+```
+
+### SlingHttpServletResponse
+
+Example for preparing a sling response which can collect the data that was written to it:
+
+```java
+// prepare sling response
+MockSlingHttpServletResponse response = new MockSlingHttpServletResponse();
+
+// execute your unit test code that writes to the response...
+
+// validate status code
+assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+
+// validate content type and content length
+assertEquals("text/plain;charset=UTF-8", response.getContentType());
+assertEquals(CharEncoding.UTF_8, response.getCharacterEncoding());
+assertEquals(55, response.getContentLength());
+
+// validate headers
+assertTrue(response.containsHeader("header1"));
+assertEquals("5", response.getHeader("header2"));
+
+// validate response body as string
+assertEquals(TEST_CONTENT, response.getOutputAsString());
+
+// validate response body as binary data
+assertArrayEquals(TEST_DATA, response.getOutput());
+```
+
+
+[jcr-mock]: http://sling.apache.org/testing/jcr-mock/
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/MockAdapterManagerTest.java b/src/test/java/org/apache/sling/testing/mock/sling/MockAdapterManagerTest.java
new file mode 100644
index 0000000..6ca7a5a
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/MockAdapterManagerTest.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.testing.mock.sling;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.apache.sling.api.adapter.AdapterFactory;
+import org.apache.sling.api.adapter.SlingAdaptable;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+
+public class MockAdapterManagerTest {
+
+    @Test
+    public void test() {
+        AdaptableTest sampleObject = new AdaptableTest();
+        assertNull(sampleObject.adaptTo(String.class));
+
+        BundleContext bundleContext = MockOsgi.newBundleContext();
+        MockSling.setAdapterManagerBundleContext(bundleContext);
+
+        bundleContext.registerService(AdapterFactory.class.getName(), new AdapterFactory() {
+            @SuppressWarnings("unchecked")
+            @Override
+            public <AdapterType> AdapterType getAdapter(final Object adaptable, final Class<AdapterType> type) {
+                if (adaptable instanceof AdaptableTest && type.isAssignableFrom(String.class)) {
+                    return (AdapterType) ((AdaptableTest) adaptable).toString();
+                }
+                return null;
+            }
+        }, null);
+
+        sampleObject = new AdaptableTest();
+        assertEquals("adaptedString", sampleObject.adaptTo(String.class));
+
+        MockSling.clearAdapterManagerBundleContext();
+
+        sampleObject = new AdaptableTest();
+        assertNull(sampleObject.adaptTo(String.class));
+    }
+
+    private static class AdaptableTest extends SlingAdaptable {
+
+        @Override
+        public String toString() {
+            return "adaptedString";
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/MockSlingRepositoryTest.java b/src/test/java/org/apache/sling/testing/mock/sling/MockSlingRepositoryTest.java
new file mode 100644
index 0000000..e1aa2e1
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/MockSlingRepositoryTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.testing.mock.sling;
+
+import static org.junit.Assert.assertNotNull;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.sling.jcr.api.SlingRepository;
+import org.apache.sling.testing.mock.jcr.MockJcr;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MockSlingRepositoryTest {
+
+    private SlingRepository repository;
+
+    @Before
+    public void setUp() {
+        this.repository = new MockSlingRepository(MockJcr.newRepository());
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testLogin() throws RepositoryException {
+        assertNotNull(this.repository.loginAdministrative(MockJcr.DEFAULT_WORKSPACE));
+        assertNotNull(this.repository.loginService("test", MockJcr.DEFAULT_WORKSPACE));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/MockSlingScriptHelperTest.java b/src/test/java/org/apache/sling/testing/mock/sling/MockSlingScriptHelperTest.java
new file mode 100644
index 0000000..bde6899
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/MockSlingScriptHelperTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+
+public class MockSlingScriptHelperTest {
+
+    private ResourceResolver resourceResolver;
+    private SlingHttpServletRequest request;
+    private SlingHttpServletResponse response;
+    private BundleContext bundleContext;
+    private SlingScriptHelper scriptHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        this.resourceResolver = MockSling.newResourceResolver();
+        this.request = new MockSlingHttpServletRequest(this.resourceResolver);
+        this.response = new MockSlingHttpServletResponse();
+        this.bundleContext = MockOsgi.newBundleContext();
+        this.scriptHelper = MockSling.newSlingScriptHelper(this.request, this.response, this.bundleContext);
+    }
+
+    @Test
+    public void testRequest() {
+        assertSame(this.request, this.scriptHelper.getRequest());
+    }
+
+    @Test
+    public void testResponse() {
+        assertSame(this.response, this.scriptHelper.getResponse());
+    }
+
+    @Test
+    public void testGetService() {
+        this.bundleContext.registerService(String.class.getName(), "test", null);
+        assertEquals("test", this.scriptHelper.getService(String.class));
+    }
+
+    @Test
+    public void testGetServices() {
+        Integer[] services = new Integer[] { 1, 2, 3 };
+        for (Integer service : services) {
+            this.bundleContext.registerService(Integer.class.getName(), service, null);
+        }
+        Integer[] servicesResult = this.scriptHelper.getServices(Integer.class, null);
+        assertArrayEquals(services, servicesResult);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/builder/ContentBuilderTest.java b/src/test/java/org/apache/sling/testing/mock/sling/builder/ContentBuilderTest.java
new file mode 100644
index 0000000..fc4ab4e
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/builder/ContentBuilderTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.testing.mock.sling.builder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+public class ContentBuilderTest {
+
+    @Rule
+    public SlingContext context = new SlingContext(ResourceResolverType.RESOURCERESOLVER_MOCK);
+
+    @Test
+    public void testResource() {
+        Resource resource = context.create().resource("/content/test1/resource1");
+        assertNotNull(resource);
+        assertEquals("resource1", resource.getName());
+        assertTrue(ResourceUtil.getValueMap(resource).isEmpty());
+    }
+
+    @Test
+    public void testResourceWithProperties() {
+        Resource resource = context.create().resource(
+                "/content/test1/resource2",
+                ImmutableMap.<String, Object> builder().put("jcr:title", "Test Title").put("stringProp", "value1")
+                        .build());
+        assertNotNull(resource);
+        assertEquals("resource2", resource.getName());
+        ValueMap props = ResourceUtil.getValueMap(resource);
+        assertEquals("Test Title", props.get("jcr:title", String.class));
+        assertEquals("value1", props.get("stringProp", String.class));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/context/SlingContextImplTest.java b/src/test/java/org/apache/sling/testing/mock/sling/context/SlingContextImplTest.java
new file mode 100644
index 0000000..eed2ec6
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/context/SlingContextImplTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.testing.mock.sling.context;
+
+import static org.junit.Assert.assertEquals;
+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 java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.scripting.SlingBindings;
+import org.apache.sling.commons.mime.MimeTypeService;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.settings.SlingSettingsService;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.loader.ContentLoader;
+import org.apache.sling.testing.mock.sling.services.MockMimeTypeService;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.ServiceReference;
+
+public class SlingContextImplTest {
+
+    private SlingContextImpl context;
+
+    @Before
+    public void setUp() throws Exception {
+        this.context = new SlingContextImpl();
+        this.context.setResourceResolverType(ResourceResolverType.RESOURCERESOLVER_MOCK);
+        this.context.setUp();
+
+        ContentLoader contentLoader = this.context.load();
+        contentLoader.json("/json-import-samples/content.json", "/content/sample/en");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        this.context.tearDown();
+    }
+    
+    @Test
+    public void testContextObjects() {
+        assertNotNull(context.componentContext());
+        assertNotNull(context.bundleContext());
+        assertNotNull(context.resourceResolver());
+        assertNotNull(context.request());
+        assertNotNull(context.requestPathInfo());
+        assertNotNull(context.response());
+        assertNotNull(context.slingScriptHelper());
+    }
+
+    @Test
+    public void testSlingBindings() {
+        SlingBindings bindings = (SlingBindings) context.request().getAttribute(SlingBindings.class.getName());
+        assertNotNull(bindings);
+        assertSame(context.request(), bindings.get(SlingBindings.REQUEST));
+        assertSame(context.response(), bindings.get(SlingBindings.RESPONSE));
+        assertSame(context.slingScriptHelper(), bindings.get(SlingBindings.SLING));
+    }
+
+    @Test
+    public void testRegisterService() {
+        Set<String> myService = new HashSet<String>();
+        context.registerService(Set.class, myService);
+
+        Set<?> serviceResult = context.getService(Set.class);
+        assertSame(myService, serviceResult);
+    }
+
+    @Test
+    public void testRegisterServiceWithProperties() {
+        Map<String, Object> props = new HashMap<String, Object>();
+        props.put("prop1", "value1");
+
+        Set<String> myService = new HashSet<String>();
+        context.registerService(Set.class, myService, props);
+
+        ServiceReference serviceReference = context.bundleContext().getServiceReference(Set.class.getName());
+        Object serviceResult = context.bundleContext().getService(serviceReference);
+        assertSame(myService, serviceResult);
+        assertEquals("value1", serviceReference.getProperty("prop1"));
+    }
+
+    @Test
+    public void testRegisterMultipleServices() {
+        Set<String> myService1 = new HashSet<String>();
+        context.registerService(Set.class, myService1);
+        Set<String> myService2 = new HashSet<String>();
+        context.registerService(Set.class, myService2);
+
+        Set[] serviceResults = context.getServices(Set.class, null);
+        assertSame(myService1, serviceResults[0]);
+        assertSame(myService2, serviceResults[1]);
+    }
+
+    @Test
+    public void testSetCurrentResource() {
+        context.currentResource("/content/sample/en/jcr:content/par/colctrl");
+        assertEquals("/content/sample/en/jcr:content/par/colctrl", context.currentResource().getPath());
+
+        context.currentResource(context.resourceResolver().getResource("/content/sample/en/jcr:content/par"));
+        assertEquals("/content/sample/en/jcr:content/par", context.currentResource().getPath());
+
+        context.currentResource((Resource) null);
+        assertNull(context.request().getResource());
+
+        context.currentResource((String) null);
+        assertNull(context.request().getResource());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetCurrentResourceNonExisting() {
+        context.currentResource("/non/existing");
+    }
+
+    @Test
+    public void testSlingModelsRequestAttribute() {
+        context.request().setAttribute("prop1", "myValue");
+        RequestAttributeModel model = context.request().adaptTo(RequestAttributeModel.class);
+        assertEquals("myValue", model.getProp1());
+    }
+
+    @Test
+    public void testSlingModelsOsgiService() {
+        context.registerService(new MockMimeTypeService());
+
+        ResourceResolver resolver = MockSling.newResourceResolver();
+        OsgiServiceModel model = resolver.adaptTo(OsgiServiceModel.class);
+        assertNotNull(model.getMimeTypeService());
+        assertEquals("text/html", model.getMimeTypeService().getMimeType("html"));
+    }
+
+    @Test
+    public void testSlingModelsInvalidAdapt() {
+        OsgiServiceModel model = context.request().adaptTo(OsgiServiceModel.class);
+        assertNull(model);
+    }
+
+    @Test
+    public void testAdaptToInterface() {
+        context.addModelsForPackage("org.apache.sling.testing.mock.sling.context");
+
+        MockSlingHttpServletRequest request = new MockSlingHttpServletRequest();
+        request.setAttribute("prop1", "myValue");
+        ServiceInterface model = request.adaptTo(ServiceInterface.class);
+        assertNotNull(model);
+        assertEquals("myValue", model.getPropValue());
+    }
+
+    @Test
+    public void testRegisterInjectActivate() {
+        context.registerInjectActivateService(new Object());
+    }
+
+    @Test
+    public void testRunModes() {
+        SlingSettingsService slingSettings = context.getService(SlingSettingsService.class);
+        assertEquals(SlingContextImpl.DEFAULT_RUN_MODES, slingSettings.getRunModes());
+
+        context.runMode("mode1", "mode2");
+        Set<String> newRunModes = slingSettings.getRunModes();
+        assertEquals(2, newRunModes.size());
+        assertTrue(newRunModes.contains("mode1"));
+        assertTrue(newRunModes.contains("mode2"));
+    }
+
+    @Model(adaptables = SlingHttpServletRequest.class)
+    public interface RequestAttributeModel {
+        @Inject
+        String getProp1();
+    }
+
+    @Model(adaptables = ResourceResolver.class)
+    public interface OsgiServiceModel {
+        @Inject
+        MimeTypeService getMimeTypeService();
+    }
+
+    public interface ServiceInterface {
+        String getPropValue();
+    }
+
+    @Model(adaptables = SlingHttpServletRequest.class, adapters = ServiceInterface.class)
+    public static class ServiceInterfaceImpl implements ServiceInterface {
+
+        @Inject
+        private String prop1;
+
+        @Override
+        public String getPropValue() {
+            return this.prop1;
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/loader/ContentLoaderBinaryTest.java b/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/loader/ContentLoaderBinaryTest.java
new file mode 100644
index 0000000..76c85c4
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/loader/ContentLoaderBinaryTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.testing.mock.sling.jcrmock.loader;
+
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.loader.AbstractContentLoaderBinaryTest;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ContentLoaderBinaryTest extends AbstractContentLoaderBinaryTest {
+
+    protected ResourceResolverType getResourceResolverType() {
+        return ResourceResolverType.JCR_MOCK;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/loader/ContentLoaderJsonDamTest.java b/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/loader/ContentLoaderJsonDamTest.java
new file mode 100644
index 0000000..ceb5b57
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/loader/ContentLoaderJsonDamTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.testing.mock.sling.jcrmock.loader;
+
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.loader.AbstractContentLoaderJsonDamTest;
+
+public class ContentLoaderJsonDamTest extends AbstractContentLoaderJsonDamTest {
+
+    protected ResourceResolverType getResourceResolverType() {
+        return ResourceResolverType.JCR_MOCK;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/loader/ContentLoaderJsonTest.java b/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/loader/ContentLoaderJsonTest.java
new file mode 100644
index 0000000..eaa297c
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/loader/ContentLoaderJsonTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.testing.mock.sling.jcrmock.loader;
+
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.loader.AbstractContentLoaderJsonTest;
+
+public class ContentLoaderJsonTest extends AbstractContentLoaderJsonTest {
+
+    protected ResourceResolverType getResourceResolverType() {
+        return ResourceResolverType.JCR_MOCK;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/resource/JcrResourceResolverTest.java b/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/resource/JcrResourceResolverTest.java
new file mode 100644
index 0000000..0e358f9
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/resource/JcrResourceResolverTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.testing.mock.sling.jcrmock.resource;
+
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.resource.AbstractJcrResourceResolverTest;
+
+/**
+ * Implements simple write and read resource and values test. JCR API is used to
+ * create the test data.
+ */
+public class JcrResourceResolverTest extends AbstractJcrResourceResolverTest {
+
+    protected ResourceResolverType getResourceResolverType() {
+        return ResourceResolverType.JCR_MOCK;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/resource/SlingCrudResourceResolverTest.java b/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/resource/SlingCrudResourceResolverTest.java
new file mode 100644
index 0000000..005ab6c
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/jcrmock/resource/SlingCrudResourceResolverTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.testing.mock.sling.jcrmock.resource;
+
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.resource.AbstractSlingCrudResourceResolverTest;
+
+/**
+ * Implements simple write and read resource and values test. Sling CRUD API is
+ * used to create the test data.
+ */
+public class SlingCrudResourceResolverTest extends AbstractSlingCrudResourceResolverTest {
+
+    protected ResourceResolverType getResourceResolverType() {
+        return ResourceResolverType.JCR_MOCK;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/junit/SlingContextTest.java b/src/test/java/org/apache/sling/testing/mock/sling/junit/SlingContextTest.java
new file mode 100644
index 0000000..24c72dd
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/junit/SlingContextTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.testing.mock.sling.junit;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+import java.io.IOException;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SlingContextTest {
+
+    private final SlingContextCallback contextSetup = mock(SlingContextCallback.class);
+    private final SlingContextCallback contextTeardown = mock(SlingContextCallback.class);
+
+    // Run all unit tests for each resource resolver types listed here
+    @Rule
+    public SlingContext context = new SlingContext(contextSetup, contextTeardown,
+            ResourceResolverType.RESOURCERESOLVER_MOCK);
+
+    @Before
+    public void setUp() throws IOException, PersistenceException {
+        verify(contextSetup).execute(context);
+    }
+
+    @Test
+    public void testRequest() {
+        assertNotNull(context.request());
+    }
+
+    @After
+    public void tearDown() {
+        // reset required because mockito gets puzzled with the parameterized
+        // JUnit rule
+        // TODO: better solution?
+        reset(contextSetup);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderBinaryTest.java b/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderBinaryTest.java
new file mode 100644
index 0000000..5765612
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderBinaryTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.testing.mock.sling.loader;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Hashtable;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.commons.mime.MimeTypeService;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.osgi.framework.BundleContext;
+
+@RunWith(MockitoJUnitRunner.class)
+public abstract class AbstractContentLoaderBinaryTest {
+
+    private static final int SAMPLE_IMAGE_FILESIZE = 62;
+
+    private BundleContext bundleContext;
+    private ResourceResolver resourceResolver;
+    private ContentLoader contentLoader;
+
+    @Mock
+    private MimeTypeService mimeTypeService;
+
+    protected abstract ResourceResolverType getResourceResolverType();
+
+    protected ResourceResolver newResourceResolver() {
+        return MockSling.newResourceResolver(getResourceResolverType());
+    }
+
+    @Before
+    public final void setUp() {
+        bundleContext = MockOsgi.newBundleContext();
+        bundleContext.registerService(MimeTypeService.class.getName(), mimeTypeService, new Hashtable());
+        resourceResolver = newResourceResolver();
+        contentLoader = new ContentLoader(this.resourceResolver, this.bundleContext);
+
+        when(mimeTypeService.getMimeType("gif")).thenReturn("image/gif");
+    }
+
+    @Test
+    public void testBinaryFile() throws IOException {
+        contentLoader.binaryFile("/sample-image.gif", "/content/binary/sample-image.gif");
+
+        Resource fileResource = resourceResolver.getResource("/content/binary/sample-image.gif");
+        assertSampleImageFileSize(fileResource);
+        assertMimeType(fileResource.getChild(JcrConstants.JCR_CONTENT), "image/gif");
+    }
+
+    @Test
+    public void testBinaryFileWithMimeType() throws IOException {
+        contentLoader.binaryFile("/sample-image.gif", "/content/binary/sample-image.gif", "mime/test");
+
+        Resource fileResource = resourceResolver.getResource("/content/binary/sample-image.gif");
+        assertSampleImageFileSize(fileResource);
+        assertMimeType(fileResource.getChild(JcrConstants.JCR_CONTENT), "mime/test");
+    }
+
+    @Test
+    public void testBinaryResource() throws IOException {
+        contentLoader.binaryResource("/sample-image.gif", "/content/binary/sample-image.gif");
+
+        Resource fileResource = resourceResolver.getResource("/content/binary/sample-image.gif");
+        assertSampleImageFileSize(fileResource);
+        assertMimeType(fileResource, "image/gif");
+    }
+
+    @Test
+    public void testBinaryResourceWithMimeType() throws IOException {
+        contentLoader.binaryResource("/sample-image.gif", "/content/binary/sample-image.gif", "mime/test");
+
+        Resource fileResource = resourceResolver.getResource("/content/binary/sample-image.gif");
+        assertSampleImageFileSize(fileResource);
+        assertMimeType(fileResource, "mime/test");
+    }
+
+    private void assertSampleImageFileSize(Resource resource) throws IOException {
+        InputStream is = resource.adaptTo(InputStream.class);
+        assertNotNull("InputSteam is null for " + resource.getPath(), is);
+        byte[] binaryData = IOUtils.toByteArray(is);
+        assertEquals(SAMPLE_IMAGE_FILESIZE, binaryData.length);
+    }
+
+    private void assertMimeType(Resource resource, String mimeType) {
+        assertNotNull(resource);
+        assertEquals(mimeType, ResourceUtil.getValueMap(resource).get(JcrConstants.JCR_MIMETYPE, String.class));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderJsonDamTest.java b/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderJsonDamTest.java
new file mode 100644
index 0000000..0f00113
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderJsonDamTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.testing.mock.sling.loader;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.junit.Before;
+import org.junit.Test;
+
+public abstract class AbstractContentLoaderJsonDamTest {
+
+    private ResourceResolver resourceResolver;
+
+    protected abstract ResourceResolverType getResourceResolverType();
+
+    protected ResourceResolver newResourceResolver() {
+        ResourceResolver resolver = MockSling.newResourceResolver(getResourceResolverType());
+
+        if (getResourceResolverType() == ResourceResolverType.JCR_MOCK) {
+            try {
+                // dummy namespace registrations to make sure sling JCR resolver
+                // does not get mixed up with the prefixes
+                NamespaceRegistry namespaceRegistry = resolver.adaptTo(Session.class).getWorkspace()
+                        .getNamespaceRegistry();
+                namespaceRegistry.registerNamespace("sling", "http://mock/sling");
+                namespaceRegistry.registerNamespace("app", "http://mock/app");
+                namespaceRegistry.registerNamespace("dam", "http://mock/dam");
+            } catch (RepositoryException ex) {
+                throw new RuntimeException("Unable to register namespaces.", ex);
+            }
+        }
+
+        return resolver;
+    }
+
+    @Before
+    public final void setUp() {
+        this.resourceResolver = newResourceResolver();
+        ContentLoader contentLoader = new ContentLoader(this.resourceResolver);
+        contentLoader.json("/json-import-samples/dam.json", "/content/dam/sample");
+    }
+
+    @Test
+    public void testDamAssetMetadata() {
+        Resource assetMetadata = this.resourceResolver
+                .getResource("/content/dam/sample/portraits/scott_reynolds.jpg/jcr:content/metadata");
+        ValueMap props = ResourceUtil.getValueMap(assetMetadata);
+
+        assertEquals("Canon\u0000", props.get("tiff:Make", String.class));
+        assertEquals((Long) 807L, props.get("tiff:ImageWidth", Long.class));
+        assertEquals((Integer) 595, props.get("tiff:ImageLength", Integer.class));
+        assertEquals(4.64385986328125d, props.get("dam:ApertureValue", Double.class), 0.00000000001d);
+
+        assertArrayEquals(new String[] { "stockphotography:business/business_people", "properties:style/color",
+                "properties:orientation/landscape" }, props.get("app:tags", String[].class));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderJsonTest.java b/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderJsonTest.java
new file mode 100644
index 0000000..778e968
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/loader/AbstractContentLoaderJsonTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.testing.mock.sling.loader;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.junit.Before;
+import org.junit.Test;
+
+public abstract class AbstractContentLoaderJsonTest {
+
+    private ResourceResolver resourceResolver;
+
+    protected abstract ResourceResolverType getResourceResolverType();
+
+    protected ResourceResolver newResourceResolver() {
+        ResourceResolver resolver = MockSling.newResourceResolver(getResourceResolverType());
+
+        if (getResourceResolverType() == ResourceResolverType.JCR_MOCK) {
+            try {
+                // dummy namespace registrations to make sure sling JCR resolver
+                // does not get mixed up with the prefixes
+                NamespaceRegistry namespaceRegistry = resolver.adaptTo(Session.class).getWorkspace()
+                        .getNamespaceRegistry();
+                namespaceRegistry.registerNamespace("sling", "http://mock/sling");
+                namespaceRegistry.registerNamespace("app", "http://mock/app");
+                namespaceRegistry.registerNamespace("dam", "http://mock/dam");
+            } catch (RepositoryException ex) {
+                throw new RuntimeException("Unable to register namespaces.", ex);
+            }
+        }
+
+        return resolver;
+    }
+
+    @Before
+    public final void setUp() {
+        this.resourceResolver = newResourceResolver();
+        ContentLoader contentLoader = new ContentLoader(this.resourceResolver);
+        contentLoader.json("/json-import-samples/content.json", "/content/sample/en");
+    }
+
+    @Test
+    public void testPageResourceType() {
+        Resource resource = this.resourceResolver.getResource("/content/sample/en");
+        assertEquals("app:Page", resource.getResourceType());
+    }
+
+    @Test
+    public void testPageJcrPrimaryType() throws RepositoryException {
+        Resource resource = this.resourceResolver.getResource("/content/sample/en");
+        assertPrimaryNodeType(resource, "app:Page");
+    }
+
+    @Test
+    public void testPageContentResourceType() {
+        Resource resource = this.resourceResolver.getResource("/content/sample/en/toolbar/profiles/jcr:content");
+        assertEquals("sample/components/contentpage", resource.getResourceType());
+    }
+
+    @Test
+    public void testPageContentJcrPrimaryType() throws RepositoryException {
+        Resource resource = this.resourceResolver.getResource("/content/sample/en/toolbar/profiles/jcr:content");
+        assertPrimaryNodeType(resource, "app:PageContent");
+    }
+
+    @Test
+    public void testPageContentProperties() {
+        Resource resource = this.resourceResolver.getResource("/content/sample/en/toolbar/profiles/jcr:content");
+        ValueMap props = ResourceUtil.getValueMap(resource);
+        assertEquals(true, props.get("hideInNav", Boolean.class));
+
+        assertEquals((Long) 1234567890123L, props.get("longProp", Long.class));
+        assertEquals(1.2345d, props.get("decimalProp", Double.class), 0.00001d);
+        assertEquals(true, props.get("booleanProp", Boolean.class));
+
+        assertArrayEquals(new Long[] { 1234567890123L, 55L }, props.get("longPropMulti", Long[].class));
+        assertArrayEquals(new Double[] { 1.2345d, 1.1d }, props.get("decimalPropMulti", Double[].class));
+        assertArrayEquals(new Boolean[] { true, false }, props.get("booleanPropMulti", Boolean[].class));
+    }
+
+    @Test
+    public void testContentResourceType() {
+        Resource resource = this.resourceResolver.getResource("/content/sample/en/jcr:content/header");
+        assertEquals("sample/components/header", resource.getResourceType());
+    }
+
+    @Test
+    public void testContentJcrPrimaryType() throws RepositoryException {
+        Resource resource = this.resourceResolver.getResource("/content/sample/en/jcr:content/header");
+        assertPrimaryNodeType(resource, JcrConstants.NT_UNSTRUCTURED);
+    }
+
+    @Test
+    public void testContentProperties() {
+        Resource resource = this.resourceResolver.getResource("/content/sample/en/jcr:content/header");
+        ValueMap props = ResourceUtil.getValueMap(resource);
+        assertEquals("/content/dam/sample/header.png", props.get("imageReference", String.class));
+    }
+
+    private void assertPrimaryNodeType(final Resource resource, final String nodeType) throws RepositoryException {
+        Node node = resource.adaptTo(Node.class);
+        if (node != null) {
+            assertEquals(nodeType, node.getPrimaryNodeType().getName());
+        } else {
+            ValueMap props = ResourceUtil.getValueMap(resource);
+            assertEquals(nodeType, props.get(JcrConstants.JCR_PRIMARYTYPE));
+        }
+    }
+
+    @Test
+    public void testCalendarEcmaFormat() {
+        Resource resource = this.resourceResolver.getResource("/content/sample/en/jcr:content");
+        ValueMap props = ResourceUtil.getValueMap(resource);
+
+        Calendar calendar = props.get("app:lastModified", Calendar.class);
+        assertNotNull(calendar);
+
+        calendar.setTimeZone(TimeZone.getTimeZone("GMT+2"));
+
+        assertEquals(2014, calendar.get(Calendar.YEAR));
+        assertEquals(4, calendar.get(Calendar.MONTH) + 1);
+        assertEquals(22, calendar.get(Calendar.DAY_OF_MONTH));
+
+        assertEquals(15, calendar.get(Calendar.HOUR_OF_DAY));
+        assertEquals(11, calendar.get(Calendar.MINUTE));
+        assertEquals(24, calendar.get(Calendar.SECOND));
+    }
+    
+    @Test
+    public void testCalendarISO8601Format() {
+        Resource resource = this.resourceResolver.getResource("/content/sample/en/jcr:content");
+        ValueMap props = ResourceUtil.getValueMap(resource);
+
+        Calendar calendar = props.get("dateISO8601String", Calendar.class);
+        assertNotNull(calendar);
+
+        calendar.setTimeZone(TimeZone.getTimeZone("GMT+2"));
+        
+        assertEquals(2014, calendar.get(Calendar.YEAR));
+        assertEquals(4, calendar.get(Calendar.MONTH) + 1);
+        assertEquals(22, calendar.get(Calendar.DAY_OF_MONTH));
+
+        assertEquals(15, calendar.get(Calendar.HOUR_OF_DAY));
+        assertEquals(11, calendar.get(Calendar.MINUTE));
+        assertEquals(24, calendar.get(Calendar.SECOND));
+    }
+    
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/resource/AbstractJcrResourceResolverTest.java b/src/test/java/org/apache/sling/testing/mock/sling/resource/AbstractJcrResourceResolverTest.java
new file mode 100644
index 0000000..f17f6d6
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/resource/AbstractJcrResourceResolverTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.testing.mock.sling.resource;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.time.DateUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Implements simple write and read resource and values test.
+ * JCR API is used to create the test data.
+ */
+public abstract class AbstractJcrResourceResolverTest {
+
+    private static final String STRING_VALUE = "value1";
+    private static final String[] STRING_ARRAY_VALUE = new String[] { "value1", "value2" };
+    private static final int INTEGER_VALUE = 25;
+    private static final double DOUBLE_VALUE = 3.555d;
+    private static final boolean BOOLEAN_VALUE = true;
+    private static final Date DATE_VALUE = new Date(10000);
+    private static final Calendar CALENDAR_VALUE = Calendar.getInstance();
+    private static final byte[] BINARY_VALUE = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
+
+    private ResourceResolver resourceResolver;
+    private Session session;
+    protected Node testRoot;
+    private static volatile long rootNodeCounter;
+
+    protected abstract ResourceResolverType getResourceResolverType();
+
+    protected ResourceResolver newResourceResolver() {
+        return MockSling.newResourceResolver(getResourceResolverType());
+    }
+
+    @Before
+    public final void setUp() throws RepositoryException {
+        resourceResolver = newResourceResolver();
+        session = resourceResolver.adaptTo(Session.class);
+
+        // prepare some test data using JCR API
+        Node rootNode = getTestRootNode();
+        Node node1 = rootNode.addNode("node1", JcrConstants.NT_UNSTRUCTURED);
+
+        node1.setProperty("stringProp", STRING_VALUE);
+        node1.setProperty("stringArrayProp", STRING_ARRAY_VALUE);
+        node1.setProperty("integerProp", INTEGER_VALUE);
+        node1.setProperty("doubleProp", DOUBLE_VALUE);
+        node1.setProperty("booleanProp", BOOLEAN_VALUE);
+        node1.setProperty("dateProp", DateUtils.toCalendar(DATE_VALUE));
+        node1.setProperty("calendarProp", CALENDAR_VALUE);
+        node1.setProperty("binaryProp", session.getValueFactory().createBinary(new ByteArrayInputStream(BINARY_VALUE)));
+
+        node1.addNode("node11", JcrConstants.NT_UNSTRUCTURED);
+        node1.addNode("node12", JcrConstants.NT_UNSTRUCTURED);
+
+        session.save();
+    }
+
+    @After
+    public final void tearDown() {
+        testRoot = null;
+    }
+
+    /**
+     * Return a test root node, created on demand, with a unique path
+     */
+    private Node getTestRootNode() throws RepositoryException {
+        if (testRoot == null) {
+            final Node root = session.getRootNode();
+            if (getResourceResolverType() == ResourceResolverType.JCR_JACKRABBIT) {
+                final Node classRoot = root.addNode(getClass().getSimpleName());
+                testRoot = classRoot.addNode(System.currentTimeMillis() + "_" + (rootNodeCounter++));
+            } else {
+                testRoot = root.addNode("test", JcrConstants.NT_UNSTRUCTURED);
+            }
+        }
+        return testRoot;
+    }
+
+    @Test
+    public void testGetResourcesAndValues() throws IOException, RepositoryException {
+        Resource resource1 = resourceResolver.getResource(getTestRootNode().getPath() + "/node1");
+        assertNotNull(resource1);
+        assertEquals("node1", resource1.getName());
+
+        ValueMap props = ResourceUtil.getValueMap(resource1);
+        assertEquals(STRING_VALUE, props.get("stringProp", String.class));
+        assertArrayEquals(STRING_ARRAY_VALUE, props.get("stringArrayProp", String[].class));
+        assertEquals((Integer) INTEGER_VALUE, props.get("integerProp", Integer.class));
+        assertEquals(DOUBLE_VALUE, props.get("doubleProp", Double.class), 0.0001);
+        assertEquals(BOOLEAN_VALUE, props.get("booleanProp", Boolean.class));
+        assertEquals(DATE_VALUE, props.get("dateProp", Date.class));
+        assertEquals(CALENDAR_VALUE.getTime(), props.get("calendarProp", Calendar.class).getTime());
+
+        Resource binaryPropResource = resource1.getChild("binaryProp");
+        InputStream is = binaryPropResource.adaptTo(InputStream.class);
+        byte[] dataFromResource = IOUtils.toByteArray(is);
+        is.close();
+        assertArrayEquals(BINARY_VALUE, dataFromResource);
+
+        // read second time to ensure not the original input stream was returned
+        InputStream is2 = binaryPropResource.adaptTo(InputStream.class);
+        byte[] dataFromResource2 = IOUtils.toByteArray(is2);
+        is2.close();
+        assertArrayEquals(BINARY_VALUE, dataFromResource2);
+
+        List<Resource> children = ImmutableList.copyOf(resource1.listChildren());
+        assertEquals(2, children.size());
+        assertEquals("node11", children.get(0).getName());
+        assertEquals("node12", children.get(1).getName());
+    }
+
+    @Test
+    public void testCreateNodeType() throws RepositoryException, PersistenceException {
+        Resource parent = resourceResolver.getResource(getTestRootNode().getPath());
+
+        Resource child = resourceResolver.create(parent, "nodeTypeResource", ImmutableMap.<String, Object> builder()
+                .put("sling:resourceType", JcrConstants.NT_UNSTRUCTURED).build());
+        assertNotNull(child);
+        assertEquals(JcrConstants.NT_UNSTRUCTURED, child.getResourceType());
+        assertEquals(JcrConstants.NT_UNSTRUCTURED, child.adaptTo(Node.class).getPrimaryNodeType().getName());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/resource/AbstractSlingCrudResourceResolverTest.java b/src/test/java/org/apache/sling/testing/mock/sling/resource/AbstractSlingCrudResourceResolverTest.java
new file mode 100644
index 0000000..20ed560
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/resource/AbstractSlingCrudResourceResolverTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.testing.mock.sling.resource;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Implements simple write and read resource and values test. Sling CRUD API is
+ * used to create the test data.
+ */
+public abstract class AbstractSlingCrudResourceResolverTest {
+
+    private static final String STRING_VALUE = "value1";
+    private static final String[] STRING_ARRAY_VALUE = new String[] { "value1", "value2" };
+    private static final int INTEGER_VALUE = 25;
+    private static final double DOUBLE_VALUE = 3.555d;
+    private static final boolean BOOLEAN_VALUE = true;
+    private static final Date DATE_VALUE = new Date(10000);
+    private static final Calendar CALENDAR_VALUE = Calendar.getInstance();
+    private static final byte[] BINARY_VALUE = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
+
+    private ResourceResolver resourceResolver;
+    protected Resource testRoot;
+    private static volatile long rootNodeCounter;
+
+    protected abstract ResourceResolverType getResourceResolverType();
+
+    protected ResourceResolver newResourceResolver() {
+        return MockSling.newResourceResolver(getResourceResolverType());
+    }
+
+    @Before
+    public final void setUp() throws IOException {
+        this.resourceResolver = newResourceResolver();
+
+        // prepare some test data using Sling CRUD API
+        Resource rootNode = getTestRootResource();
+
+        Map<String, Object> props = new HashMap<String, Object>();
+        props.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+        props.put("stringProp", STRING_VALUE);
+        props.put("stringArrayProp", STRING_ARRAY_VALUE);
+        props.put("integerProp", INTEGER_VALUE);
+        props.put("doubleProp", DOUBLE_VALUE);
+        props.put("booleanProp", BOOLEAN_VALUE);
+        props.put("dateProp", DATE_VALUE);
+        props.put("calendarProp", CALENDAR_VALUE);
+        props.put("binaryProp", new ByteArrayInputStream(BINARY_VALUE));
+        Resource node1 = this.resourceResolver.create(rootNode, "node1", props);
+
+        this.resourceResolver.create(node1, "node11", ValueMap.EMPTY);
+        this.resourceResolver.create(node1, "node12", ValueMap.EMPTY);
+
+        this.resourceResolver.commit();
+    }
+
+    @After
+    public final void tearDown() {
+        this.testRoot = null;
+    }
+
+    /**
+     * Return a test root resource, created on demand, with a unique path
+     * @throws PersistenceException
+     */
+    private Resource getTestRootResource() throws PersistenceException {
+        if (this.testRoot == null) {
+            Map<String, Object> props = new HashMap<String, Object>();
+            props.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_UNSTRUCTURED);
+            final Resource root = this.resourceResolver.getResource("/");
+            if (getResourceResolverType() == ResourceResolverType.JCR_JACKRABBIT) {
+                final Resource classRoot = this.resourceResolver.create(root, getClass().getSimpleName(), props);
+                this.testRoot = this.resourceResolver.create(classRoot, System.currentTimeMillis() + "_"
+                        + (rootNodeCounter++), props);
+            } else {
+                this.testRoot = this.resourceResolver.create(root, "test", props);
+            }
+        }
+        return this.testRoot;
+    }
+
+    @Test
+    public void testSimpleProperties() throws IOException {
+        Resource resource1 = this.resourceResolver.getResource(getTestRootResource().getPath() + "/node1");
+        assertNotNull(resource1);
+        assertEquals("node1", resource1.getName());
+
+        ValueMap props = ResourceUtil.getValueMap(resource1);
+        assertEquals(STRING_VALUE, props.get("stringProp", String.class));
+        assertArrayEquals(STRING_ARRAY_VALUE, props.get("stringArrayProp", String[].class));
+        assertEquals((Integer) INTEGER_VALUE, props.get("integerProp", Integer.class));
+        assertEquals(DOUBLE_VALUE, props.get("doubleProp", Double.class), 0.0001);
+        assertEquals(BOOLEAN_VALUE, props.get("booleanProp", Boolean.class));
+    }
+
+    @Test
+    public void testDateProperty() throws IOException {
+        Resource resource1 = this.resourceResolver.getResource(getTestRootResource().getPath() + "/node1");
+        ValueMap props = ResourceUtil.getValueMap(resource1);
+        // TODO: enable this test when JCR resource implementation supports
+        // writing Date objects (SLING-3846)
+        if (getResourceResolverType() != ResourceResolverType.JCR_MOCK
+                && getResourceResolverType() != ResourceResolverType.JCR_JACKRABBIT) {
+            assertEquals(DATE_VALUE, props.get("dateProp", Date.class));
+        }
+    }
+
+    @Test
+    public void testDatePropertyToCalendar() throws IOException {
+        Resource resource1 = this.resourceResolver.getResource(getTestRootResource().getPath() + "/node1");
+        ValueMap props = ResourceUtil.getValueMap(resource1);
+        // TODO: enable this test when JCR resource implementation supports
+        // writing Date objects (SLING-3846)
+        if (getResourceResolverType() != ResourceResolverType.JCR_MOCK
+                && getResourceResolverType() != ResourceResolverType.JCR_JACKRABBIT) {
+            Calendar calendarValue = props.get("dateProp", Calendar.class);
+            assertNotNull(calendarValue);
+            assertEquals(DATE_VALUE, calendarValue.getTime());
+        }
+    }
+
+    @Test
+    public void testCalendarProperty() throws IOException {
+        Resource resource1 = this.resourceResolver.getResource(getTestRootResource().getPath() + "/node1");
+        ValueMap props = ResourceUtil.getValueMap(resource1);
+        assertEquals(CALENDAR_VALUE.getTime(), props.get("calendarProp", Calendar.class).getTime());
+    }
+
+    @Test
+    public void testCalendarPropertyToDate() throws IOException {
+        Resource resource1 = this.resourceResolver.getResource(getTestRootResource().getPath() + "/node1");
+        ValueMap props = ResourceUtil.getValueMap(resource1);
+        Date dateValue = props.get("calendarProp", Date.class);
+        assertNotNull(dateValue);
+        assertEquals(CALENDAR_VALUE.getTime(), dateValue);
+    }
+
+    @Test
+    public void testListChildren() throws IOException {
+        Resource resource1 = this.resourceResolver.getResource(getTestRootResource().getPath() + "/node1");
+
+        List<Resource> children = ImmutableList.copyOf(resource1.listChildren());
+        assertEquals(2, children.size());
+        assertEquals("node11", children.get(0).getName());
+        assertEquals("node12", children.get(1).getName());
+    }
+
+    @Test
+    public void testBinaryData() throws IOException {
+        Resource resource1 = this.resourceResolver.getResource(getTestRootResource().getPath() + "/node1");
+
+        Resource binaryPropResource = resource1.getChild("binaryProp");
+        InputStream is = binaryPropResource.adaptTo(InputStream.class);
+        byte[] dataFromResource = IOUtils.toByteArray(is);
+        is.close();
+        assertArrayEquals(BINARY_VALUE, dataFromResource);
+
+        // read second time to ensure not the original input stream was returned
+        // and this time using another syntax
+        InputStream is2 = ResourceUtil.getValueMap(resource1).get("binaryProp", InputStream.class);
+        byte[] dataFromResource2 = IOUtils.toByteArray(is2);
+        is2.close();
+        assertArrayEquals(BINARY_VALUE, dataFromResource2);
+    }
+
+    @Test
+    public void testPrimaryTypeResourceType() throws PersistenceException {
+        Resource resource = this.resourceResolver.getResource(getTestRootResource().getPath());
+        assertEquals(JcrConstants.NT_UNSTRUCTURED, resource.getResourceType());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/rrmock/loader/ContentLoaderBinaryTest.java b/src/test/java/org/apache/sling/testing/mock/sling/rrmock/loader/ContentLoaderBinaryTest.java
new file mode 100644
index 0000000..4d5eebf
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/rrmock/loader/ContentLoaderBinaryTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling.rrmock.loader;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.loader.AbstractContentLoaderBinaryTest;
+
+public class ContentLoaderBinaryTest extends AbstractContentLoaderBinaryTest {
+
+    @Override
+    protected ResourceResolverType getResourceResolverType() {
+        return ResourceResolverType.RESOURCERESOLVER_MOCK;
+    }
+
+    @Override
+    protected ResourceResolver newResourceResolver() {
+        return MockSling.newResourceResolver(getResourceResolverType());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/rrmock/loader/ContentLoaderJsonDamTest.java b/src/test/java/org/apache/sling/testing/mock/sling/rrmock/loader/ContentLoaderJsonDamTest.java
new file mode 100644
index 0000000..4f6678d
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/rrmock/loader/ContentLoaderJsonDamTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling.rrmock.loader;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.loader.AbstractContentLoaderJsonDamTest;
+
+public class ContentLoaderJsonDamTest extends AbstractContentLoaderJsonDamTest {
+
+    @Override
+    protected ResourceResolverType getResourceResolverType() {
+        return ResourceResolverType.RESOURCERESOLVER_MOCK;
+    }
+
+    @Override
+    protected ResourceResolver newResourceResolver() {
+        return MockSling.newResourceResolver(getResourceResolverType());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/rrmock/loader/ContentLoaderJsonTest.java b/src/test/java/org/apache/sling/testing/mock/sling/rrmock/loader/ContentLoaderJsonTest.java
new file mode 100644
index 0000000..0c33fd5
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/rrmock/loader/ContentLoaderJsonTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling.rrmock.loader;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.loader.AbstractContentLoaderJsonTest;
+
+public class ContentLoaderJsonTest extends AbstractContentLoaderJsonTest {
+
+    @Override
+    protected ResourceResolverType getResourceResolverType() {
+        return ResourceResolverType.RESOURCERESOLVER_MOCK;
+    }
+
+    @Override
+    protected ResourceResolver newResourceResolver() {
+        return MockSling.newResourceResolver(getResourceResolverType());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/rrmock/resource/SlingCrudResourceResolverTest.java b/src/test/java/org/apache/sling/testing/mock/sling/rrmock/resource/SlingCrudResourceResolverTest.java
new file mode 100644
index 0000000..5bca11e
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/rrmock/resource/SlingCrudResourceResolverTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling.rrmock.resource;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.resource.AbstractSlingCrudResourceResolverTest;
+
+public class SlingCrudResourceResolverTest extends AbstractSlingCrudResourceResolverTest {
+
+    @Override
+    protected ResourceResolverType getResourceResolverType() {
+        return ResourceResolverType.RESOURCERESOLVER_MOCK;
+    }
+
+    @Override
+    protected ResourceResolver newResourceResolver() {
+        return MockSling.newResourceResolver(getResourceResolverType());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/services/MockMimeTypeServiceTest.java b/src/test/java/org/apache/sling/testing/mock/sling/services/MockMimeTypeServiceTest.java
new file mode 100644
index 0000000..edd5b14
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/services/MockMimeTypeServiceTest.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.testing.mock.sling.services;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.sling.commons.mime.MimeTypeService;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MockMimeTypeServiceTest {
+
+    private MimeTypeService mimeTypeService;
+
+    @Before
+    public void setUp() throws Exception {
+        this.mimeTypeService = new MockMimeTypeService();
+    }
+
+    @Test
+    public void testGetMimeType() {
+        assertEquals("text/html", this.mimeTypeService.getMimeType("html"));
+        assertEquals("application/json", this.mimeTypeService.getMimeType("json"));
+        assertEquals("image/jpeg", this.mimeTypeService.getMimeType("jpg"));
+        assertEquals("image/jpeg", this.mimeTypeService.getMimeType("jpeg"));
+    }
+
+    @Test
+    public void testGetExtension() {
+        assertEquals("html", this.mimeTypeService.getExtension("text/html"));
+        assertEquals("json", this.mimeTypeService.getExtension("application/json"));
+        assertEquals("jpeg", this.mimeTypeService.getExtension("image/jpeg"));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/services/MockModelAdapterFactoryTest.java b/src/test/java/org/apache/sling/testing/mock/sling/services/MockModelAdapterFactoryTest.java
new file mode 100644
index 0000000..a4138c3
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/services/MockModelAdapterFactoryTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.testing.mock.sling.services;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import javax.inject.Inject;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.adapter.AdapterFactory;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.commons.mime.MimeTypeService;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.impl.FirstImplementationPicker;
+import org.apache.sling.models.impl.injectors.OSGiServiceInjector;
+import org.apache.sling.models.impl.injectors.RequestAttributeInjector;
+import org.apache.sling.models.spi.ImplementationPicker;
+import org.apache.sling.models.spi.Injector;
+import org.apache.sling.testing.mock.osgi.MockOsgi;
+import org.apache.sling.testing.mock.sling.MockSling;
+import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.ComponentContext;
+
+public class MockModelAdapterFactoryTest {
+
+    private ComponentContext componentContext;
+    private BundleContext bundleContext;
+
+    @Before
+    public void setUp() throws Exception {
+        componentContext = MockOsgi.newComponentContext();
+        bundleContext = componentContext.getBundleContext();
+        MockSling.setAdapterManagerBundleContext(bundleContext);
+
+        // register sling models adapter factory
+        MockModelAdapterFactory mockModelAdapterFactory = new MockModelAdapterFactory(componentContext);
+        bundleContext.registerService(AdapterFactory.class.getName(), mockModelAdapterFactory, null);
+
+        // register some injectors
+        bundleContext.registerService(Injector.class.getName(), new RequestAttributeInjector(), null);
+        OSGiServiceInjector osgiServiceInjector = new OSGiServiceInjector();
+        osgiServiceInjector.activate(componentContext);
+        bundleContext.registerService(Injector.class.getName(), osgiServiceInjector, null);
+
+        // register implementation pickers
+        bundleContext.registerService(ImplementationPicker.class.getName(), new FirstImplementationPicker(), null);
+
+        // scan for @Model classes
+        mockModelAdapterFactory.addModelsForPackage("org.apache.sling.testing.mock.sling.services");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        MockSling.clearAdapterManagerBundleContext();
+    }
+
+    @Test
+    public void testRequestAttribute() {
+        MockSlingHttpServletRequest request = new MockSlingHttpServletRequest();
+        request.setAttribute("prop1", "myValue");
+        RequestAttributeModel model = request.adaptTo(RequestAttributeModel.class);
+        assertNotNull(model);
+        assertEquals("myValue", model.getProp1());
+    }
+
+    @Test
+    public void testOsgiService() {
+        bundleContext.registerService(MimeTypeService.class.getName(), new MockMimeTypeService(), null);
+
+        ResourceResolver resolver = MockSling.newResourceResolver();
+        OsgiServiceModel model = resolver.adaptTo(OsgiServiceModel.class);
+        assertNotNull(model);
+        assertNotNull(model.getMimeTypeService());
+        assertEquals("text/html", model.getMimeTypeService().getMimeType("html"));
+    }
+
+    @Test
+    public void testInvalidAdapt() {
+        MockSlingHttpServletRequest request = new MockSlingHttpServletRequest();
+        OsgiServiceModel model = request.adaptTo(OsgiServiceModel.class);
+        assertNull(model);
+    }
+
+    @Test
+    public void testAdaptToInterface() {
+        MockSlingHttpServletRequest request = new MockSlingHttpServletRequest();
+        request.setAttribute("prop1", "myValue");
+        ServiceInterface model = request.adaptTo(ServiceInterface.class);
+        assertNotNull(model);
+        assertEquals("myValue", model.getPropValue());
+    }
+
+    @Model(adaptables = SlingHttpServletRequest.class)
+    public interface RequestAttributeModel {
+        @Inject
+        String getProp1();
+    }
+
+    @Model(adaptables = ResourceResolver.class)
+    public interface OsgiServiceModel {
+        @Inject
+        MimeTypeService getMimeTypeService();
+    }
+
+    public interface ServiceInterface {
+        String getPropValue();
+    }
+
+    @Model(adaptables = SlingHttpServletRequest.class, adapters = ServiceInterface.class)
+    public static class ServiceInterfaceImpl implements ServiceInterface {
+        @Inject
+        private String prop1;
+
+        @Override
+        public String getPropValue() {
+            return this.prop1;
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/services/MockSlingSettingServiceTest.java b/src/test/java/org/apache/sling/testing/mock/sling/services/MockSlingSettingServiceTest.java
new file mode 100644
index 0000000..10f0088
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/services/MockSlingSettingServiceTest.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.testing.mock.sling.services;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Set;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+public class MockSlingSettingServiceTest {
+
+    @Test
+    public void testDefaultRunModes() {
+        Set<String> defaultRunModes = ImmutableSet.<String> builder().add("mode0").build();
+        MockSlingSettingService underTest = new MockSlingSettingService(defaultRunModes);
+        assertEquals(defaultRunModes, underTest.getRunModes());
+
+        Set<String> newRunModes = ImmutableSet.<String> builder().add("mode1").add("mode2").build();
+        underTest.setRunModes(newRunModes);
+        assertEquals(newRunModes, underTest.getRunModes());
+    }
+
+    @Test
+    public void testNoDefaultRunModes() {
+        MockSlingSettingService underTest = new MockSlingSettingService();
+        assertTrue(underTest.getRunModes().isEmpty());
+
+        Set<String> newRunModes = ImmutableSet.<String> builder().add("mode1").add("mode2").build();
+        underTest.setRunModes(newRunModes);
+        assertEquals(newRunModes, underTest.getRunModes());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockHttpSessionTest.java b/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockHttpSessionTest.java
new file mode 100644
index 0000000..85e0612
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockHttpSessionTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling.servlet;
+
+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 javax.servlet.http.HttpSession;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class MockHttpSessionTest {
+
+    private HttpSession httpSession;
+
+    @Before
+    public void setUp() throws Exception {
+        this.httpSession = new MockHttpSession();
+    }
+
+    @Test
+    public void testServletContext() {
+        assertNotNull(this.httpSession.getServletContext());
+    }
+
+    @Test
+    public void testId() {
+        assertNotNull(this.httpSession.getId());
+    }
+
+    @Test
+    public void testCreationTime() {
+        assertNotNull(this.httpSession.getCreationTime());
+    }
+
+    @Test
+    public void testAttributes() {
+        this.httpSession.setAttribute("attr1", "value1");
+        assertTrue(this.httpSession.getAttributeNames().hasMoreElements());
+        assertEquals("value1", this.httpSession.getAttribute("attr1"));
+        this.httpSession.removeAttribute("attr1");
+        assertFalse(this.httpSession.getAttributeNames().hasMoreElements());
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testValues() {
+        this.httpSession.putValue("attr1", "value1");
+        assertEquals(1, this.httpSession.getValueNames().length);
+        assertEquals("value1", this.httpSession.getValue("attr1"));
+        this.httpSession.removeValue("attr1");
+        assertEquals(0, this.httpSession.getValueNames().length);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockRequestPathInfoTest.java b/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockRequestPathInfoTest.java
new file mode 100644
index 0000000..006a415
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockRequestPathInfoTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.testing.mock.sling.servlet;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+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/testing/mock/sling/servlet/MockServletContextTest.java b/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockServletContextTest.java
new file mode 100644
index 0000000..4c7b116
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockServletContextTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.sling.servlet;
+
+import static org.junit.Assert.assertEquals;
+
+import javax.servlet.ServletContext;
+
+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/testing/mock/sling/servlet/MockSlingHttpServletRequestTest.java b/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockSlingHttpServletRequestTest.java
new file mode 100644
index 0000000..7539d09
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockSlingHttpServletRequestTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.testing.mock.sling.servlet;
+
+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 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 javax.servlet.http.Cookie;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.lang3.CharEncoding;
+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(new MockSlingHttpServletRequest().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());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockSlingHttpServletResponseTest.java b/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockSlingHttpServletResponseTest.java
new file mode 100644
index 0000000..e794909
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/sling/servlet/MockSlingHttpServletResponseTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.testing.mock.sling.servlet;
+
+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.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());
+        assertEquals(CharEncoding.ISO_8859_1, response.getCharacterEncoding());
+
+        response.setContentType("text/plain;charset=UTF-8");
+        assertEquals("text/plain;charset=UTF-8", response.getContentType());
+        assertEquals(CharEncoding.UTF_8, 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());
+    }
+
+}
diff --git a/src/test/resources/json-import-samples/content.json b/src/test/resources/json-import-samples/content.json
new file mode 100644
index 0000000..b11dbf9
--- /dev/null
+++ b/src/test/resources/json-import-samples/content.json
@@ -0,0 +1,257 @@
+{
+  "jcr:primaryType": "app:Page",
+  "jcr:createdBy": "admin",
+  "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+  "jcr:content": {
+    "jcr:primaryType": "app:PageContent",
+    "jcr:createdBy": "admin",
+    "jcr:title": "English",
+    "app:template": "/apps/sample/templates/homepage",
+    "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+    "app:lastModified": "Tue Apr 22 2014 15:11:24 GMT+0200",
+    "dateISO8601String": "2014-04-22T15:11:24.000+02:00",
+    "pageTitle": "Sample Homepage",
+    "sling:resourceType": "sample/components/homepage",
+    "app:designPath": "/etc/designs/sample",
+    "app:lastModifiedBy": "admin",
+    "par": {
+      "jcr:primaryType": "nt:unstructured",
+      "sling:resourceType": "foundation/components/parsys",
+      "colctrl": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:lastModifiedBy": "admin",
+        "layout": "2;colctrl-lt0",
+        "jcr:created": "Mon Aug 23 2010 22:02:24 GMT+0200",
+        "jcr:lastModified": "Mon Aug 23 2010 22:02:35 GMT+0200",
+        "sling:resourceType": "foundation/components/parsys/colctrl"
+      },
+      "image": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "fileReference": "/content/dam/sample/portraits/jane_doe.jpg",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:03:39 GMT+0200",
+        "width": "340",
+        "jcr:lastModified": "Sun Oct 31 2010 21:39:50 GMT+0100",
+        "sling:resourceType": "foundation/components/image",
+        "file": {
+          "jcr:primaryType": "nt:file",
+          "jcr:createdBy": "admin",
+          "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+          "jcr:content": {
+            "jcr:primaryType": "nt:resource",
+            "jcr:lastModifiedBy": "anonymous",
+            "jcr:mimeType": "image/jpeg",
+            "jcr:lastModified": "Thu Aug 07 2014 16:32:59 GMT+0200",
+            ":jcr:data": 24377,
+            "jcr:uuid": "eda76d00-b2cd-4b59-878f-c33f71ceaddc"
+          }
+        }
+      },
+      "title_1": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:title": "Strategic Consulting",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:12:08 GMT+0200",
+        "jcr:lastModified": "Wed Oct 27 2010 21:33:24 GMT+0200",
+        "sling:resourceType": "sample/components/title"
+      },
+      "text_1": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Sun Oct 31 2010 21:48:04 GMT+0100",
+        "text": "<p><span class=\"Apple-style-span\" style=\"font-size: 12px;\">In&nbsp;today's competitive market, organizations can face several key geometric challenges:<\/span><\/p>\n<ul>\n<li><span class=\"Apple-style-span\" style=\"font-size: 12px;\">Polyhedral Sectioning<\/span><\/li>\n<li><span class=\"Apple-style-span\" style=\"font-size: 12px;\">Triangulation&nbsp;<\/span><\/li>\n<li><span class=\"Apple-style-span\" style=\"font-size: 12px;\">Trigonometric Calculation<\/span><\ [...]
+        "jcr:lastModified": "Sun Oct 31 2010 21:49:06 GMT+0100",
+        "sling:resourceType": "foundation/components/text",
+        "textIsRich": "true"
+      },
+      "col_break12825937554040": {
+        "jcr:primaryType": "nt:unstructured",
+        "controlType": "break",
+        "sling:resourceType": "foundation/components/parsys/colctrl"
+      },
+      "image_0": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "fileReference": "/content/dam/sample/offices/clean_room.jpg",
+        "height": "226",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:04:46 GMT+0200",
+        "jcr:lastModified": "Fri Nov 05 2010 10:38:15 GMT+0100",
+        "sling:resourceType": "foundation/components/image",
+        "imageRotate": "0",
+        "file": {
+          "jcr:primaryType": "nt:file",
+          "jcr:createdBy": "admin",
+          "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+          "jcr:content": {
+            "jcr:primaryType": "nt:resource",
+            "jcr:lastModifiedBy": "anonymous",
+            "jcr:mimeType": "image/jpeg",
+            "jcr:lastModified": "Thu Aug 07 2014 16:32:59 GMT+0200",
+            ":jcr:data": 21142,
+            "jcr:uuid": "6139077f-191f-4337-aaef-55456ebe6784"
+          }
+        }
+      },
+      "title_2": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:title": "Shape Technology",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:12:13 GMT+0200",
+        "jcr:lastModified": "Tue Oct 26 2010 21:16:29 GMT+0200",
+        "sling:resourceType": "sample/components/title"
+      },
+      "text_0": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:16:30 GMT+0200",
+        "text": "<p>The Sample investment in R&amp;D has done more than solidify our industry leadership role, we have now outpaced our competitors to such an extent that we are in an altogether new space.<\/p>\n<p>This is why our high quality polygons and polyhedra provide the only turnkey solutions across the whole range of euclidean geometry. And our mathematicians are working on the next generation of fractal curves to bring you shapes that are unthinkable today.<\/p>\n<p><\/p>\n<p>< [...]
+        "jcr:lastModified": "Mon Nov 08 2010 20:39:00 GMT+0100",
+        "sling:resourceType": "foundation/components/text",
+        "textIsRich": "true"
+      },
+      "col_end12825937444810": {
+        "jcr:primaryType": "nt:unstructured",
+        "controlType": "end",
+        "sling:resourceType": "foundation/components/parsys/colctrl"
+      }
+    },
+    "header": {
+      "jcr:primaryType": "nt:unstructured",
+      "jcr:title": "trust our experience\r\nto manage your business",
+      "imageReference": "/content/dam/sample/header.png",
+      "text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc eget neque. Nunc condimentum ipsum et orci. Aenean est. Cras eget diam. read more",
+      "sling:resourceType": "sample/components/header"
+    },
+    "newslist": {
+      "jcr:primaryType": "nt:unstructured",
+      "headline": "trust our experience\nto manage your business",
+      "text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc eget neque. Nunc condimentum ipsum et orci. Aenean est. Cras eget diam. read more",
+      "sling:resourceType": "sample/components/listchildren",
+      "listroot": "/content/sample/en/about/news"
+    },
+    "lead": {
+      "jcr:primaryType": "nt:unstructured",
+      "jcr:title": "World Leader in Applied Geometry ",
+      "jcr:lastModifiedBy": "admin",
+      "text": "Lead Text",
+      "title": "Lead Title",
+      "jcr:description": "Sample has been selling and servicing shapes for over 2000 years. From our beginnings as a small vendor of squares and rectangles we have grown our business into a leading global provider of platonic solids and fractals. Join us as we lead geometry into the future.",
+      "jcr:lastModified": "Wed Jan 19 2011 14:35:29 GMT+0100",
+      "sling:resourceType": "sample/components/lead",
+      "app:annotations": {"jcr:primaryType": "nt:unstructured"}
+    },
+    "image": {
+      "jcr:primaryType": "nt:unstructured",
+      "jcr:lastModifiedBy": "admin",
+      "jcr:lastModified": "Wed Oct 27 2010 21:30:59 GMT+0200",
+      "imageRotate": "0"
+    },
+    "carousel": {
+      "jcr:primaryType": "nt:unstructured",
+      "playSpeed": "6000",
+      "jcr:lastModifiedBy": "admin",
+      "pages": [
+        "/content/sample/en/events/techsummit",
+        "/content/sample/en/events/userconf",
+        "/content/sample/en/events/shapecon",
+        "/content/sample/en/events/dsc"
+      ],
+      "jcr:lastModified": "Tue Oct 05 2010 14:14:27 GMT+0200",
+      "transTime": "1000",
+      "sling:resourceType": "foundation/components/carousel",
+      "listFrom": "static"
+    },
+    "rightpar": {
+      "jcr:primaryType": "nt:unstructured",
+      "sling:resourceType": "foundation/components/parsys",
+      "teaser": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Tue Jan 25 2011 11:30:09 GMT+0100",
+        "campaignpath": "/content/campaigns/sample",
+        "jcr:lastModified": "Wed Feb 02 2011 08:40:30 GMT+0100",
+        "sling:resourceType": "personalization/components/teaser"
+      }
+    }
+  },
+  "toolbar": {
+    "jcr:primaryType": "app:Page",
+    "jcr:createdBy": "admin",
+    "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
+    "jcr:content": {
+      "jcr:primaryType": "app:PageContent",
+      "subtitle": "Contains the toolbar",
+      "jcr:createdBy": "admin",
+      "jcr:title": "Toolbar",
+      "app:template": "/apps/sample/templates/contentpage",
+      "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
+      "app:lastModified": "Wed Aug 25 2010 22:51:02 GMT+0200",
+      "hideInNav": "true",
+      "sling:resourceType": "sample/components/contentpage",
+      "app:lastModifiedBy": "admin",
+      "par": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "foundation/components/parsys"
+      },
+      "rightpar": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "foundation/components/iparsys",
+        "iparsys_fake_par": {
+          "jcr:primaryType": "nt:unstructured",
+          "sling:resourceType": "foundation/components/iparsys/par"
+        }
+      }
+    },
+    "profiles": {
+      "jcr:primaryType": "app:Page",
+      "jcr:createdBy": "admin",
+      "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
+      "jcr:content": {
+        "jcr:primaryType": "app:PageContent",
+        "jcr:createdBy": "admin",
+        "jcr:title": "Profiles",
+        "app:template": "/apps/sample/templates/contentpage",
+        "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
+        "app:lastModified": "Thu Nov 05 2009 20:27:13 GMT+0100",
+        "hideInNav": true,
+        "sling:resourceType": "sample/components/contentpage",
+        "app:lastModifiedBy": "admin",
+        "longProp": 1234567890123,
+        "decimalProp": 1.2345,
+        "booleanProp": true,
+        "longPropMulti": [1234567890123,55],
+        "decimalPropMulti": [1.2345,1.1],
+        "booleanPropMulti": [true,false],
+        "par": {
+          "jcr:primaryType": "nt:unstructured",
+          "sling:resourceType": "foundation/components/parsys",
+          "textimage": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "foundation/components/textimage"
+          },
+          "mygadgets": {
+            "jcr:primaryType": "nt:unstructured",
+            "gadgets": "http://customer.meteogroup.de/meteogroup/gadgets/wetter24.xml\nhttp://germanweatherradar.googlecode.com/svn/trunk/german-weather-radar.xml\nhttp://www.digitalpowered.info/gadget/ski.pictures.xml\nhttp://www.canbuffi.de/gadgets/clock/clock.xml",
+            "sling:resourceType": "personalization/components/mygadgets"
+          }
+        },
+        "rightpar": {
+          "jcr:primaryType": "nt:unstructured",
+          "sling:resourceType": "foundation/components/iparsys",
+          "iparsys_fake_par": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "foundation/components/iparsys/par"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/src/test/resources/json-import-samples/dam.json b/src/test/resources/json-import-samples/dam.json
new file mode 100644
index 0000000..04f1089
--- /dev/null
+++ b/src/test/resources/json-import-samples/dam.json
@@ -0,0 +1,156 @@
+{
+  "jcr:primaryType": "sling:OrderedFolder",
+  "jcr:createdBy": "admin",
+  "jcr:title": "Sample",
+  "jcr:created": "Thu Aug 07 2014 16:32:56 GMT+0200",
+  "portraits": {
+    "jcr:primaryType": "sling:OrderedFolder",
+    "jcr:createdBy": "admin",
+    "jcr:title": "Portraits",
+    "jcr:created": "Thu Aug 07 2014 16:32:57 GMT+0200",
+    "scott_reynolds.jpg": {
+      "jcr:primaryType": "dam:Asset",
+      "jcr:mixinTypes": ["mix:versionable"],
+      "jcr:createdBy": "admin",
+      "jcr:versionHistory": "d56a56fa-2a34-487d-b349-53b51033ffc4",
+      "jcr:predecessors": ["f08611ae-e7ed-4b85-99fa-2c4a623e49c2"],
+      "jcr:created": "Thu Aug 07 2014 16:32:58 GMT+0200",
+      "jcr:baseVersion": "f08611ae-e7ed-4b85-99fa-2c4a623e49c2",
+      "jcr:isCheckedOut": true,
+      "jcr:uuid": "442d55b6-d534-4faf-9394-c9c20d095985",
+      "jcr:content": {
+        "jcr:primaryType": "dam:AssetContent",
+        "jcr:lastModifiedBy": "admin",
+        "app:name": "scott_reynolds.jpg",
+        "jcr:lastModified": "Wed May 08 2013 10:21:57 GMT+0200",
+        "app:parentPath": "/content/dam/sample/portraits",
+        "renditions": {
+          "jcr:primaryType": "nt:folder",
+          "jcr:createdBy": "admin",
+          "jcr:created": "Thu Aug 07 2014 16:32:58 GMT+0200",
+          "dam.thumbnail.48.48.png": {
+            "jcr:primaryType": "nt:file",
+            "jcr:createdBy": "admin",
+            "jcr:created": "Thu Aug 07 2014 16:32:58 GMT+0200",
+            "jcr:content": {
+              "jcr:primaryType": "nt:resource",
+              "jcr:lastModifiedBy": "admin",
+              "jcr:mimeType": "image/png",
+              "jcr:lastModified": "Thu Aug 07 2014 16:32:58 GMT+0200",
+              ":jcr:data": 5071,
+              "jcr:uuid": "1a8cda3f-ac06-4779-89e2-55e3929cfc3e"
+            }
+          },
+          "dam.thumbnail.140.100.png": {
+            "jcr:primaryType": "nt:file",
+            "jcr:createdBy": "admin",
+            "jcr:created": "Thu Aug 07 2014 16:32:58 GMT+0200",
+            "jcr:content": {
+              "jcr:primaryType": "nt:resource",
+              "jcr:lastModifiedBy": "admin",
+              "jcr:mimeType": "image/png",
+              "jcr:lastModified": "Thu Aug 07 2014 16:32:58 GMT+0200",
+              ":jcr:data": 34023,
+              "jcr:uuid": "5cbcbf47-a33e-4f33-b9d9-55c9d5f7dd85"
+            }
+          },
+          "original": {
+            "jcr:primaryType": "nt:file",
+            "jcr:createdBy": "admin",
+            "jcr:created": "Thu Aug 07 2014 16:32:58 GMT+0200",
+            "jcr:content": {
+              "jcr:primaryType": "nt:resource",
+              "jcr:lastModifiedBy": "admin",
+              "jcr:mimeType": "image/jpeg",
+              "jcr:lastModified": "Thu Aug 07 2014 16:32:58 GMT+0200",
+              ":jcr:data": 46825,
+              "jcr:uuid": "5673bb2d-0fc3-485d-9767-57241c1b4a30"
+            }
+          },
+          "dam.thumbnail.319.319.png": {
+            "jcr:primaryType": "nt:file",
+            "jcr:createdBy": "admin",
+            "jcr:created": "Thu Aug 07 2014 16:32:58 GMT+0200",
+            "jcr:content": {
+              "jcr:primaryType": "nt:resource",
+              "jcr:lastModifiedBy": "admin",
+              "jcr:mimeType": "image/png",
+              "jcr:lastModified": "Thu Aug 07 2014 16:32:58 GMT+0200",
+              ":jcr:data": 162594,
+              "jcr:uuid": "176a4ba8-c03b-4f90-815e-d84070d15dc2"
+            }
+          }
+        },
+        "metadata": {
+          "jcr:primaryType": "nt:unstructured",
+          "jcr:mixinTypes": ["app:Taggable"],
+          "dam:UserComment": "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000 [...]
+          "dam:ExposureCompensation": 2,
+          "tiff:Compression": 6,
+          "tiff:Make": "Canon\u0000",
+          "dam:Physicalwidthininches": 4.483333110809326,
+          "exif:CustomRendered": 0,
+          "dam:JpgFromRawStart": 1054,
+          "dam:Fileformat": "JPEG",
+          "exif:MaxApertureValue": 4.64385986328125,
+          "dam:Progressive": "no",
+          "exif:FNumber": 5,
+          "tiff:ImageLength": 595,
+          "exif:FlashpixVersion": "0100",
+          "tiff:YResolution": 180,
+          "dam:OtherImageStart": 1054,
+          "exif:FocalLength": 39,
+          "exif:ColorSpace": 65535,
+          "jcr:lastModifiedBy": "admin",
+          "xmp:CreatorTool": "Adobe Photoshop 7.0",
+          "dam:SensingMethod": 2,
+          "dam:extracted": "Wed Oct 06 2010 15:53:22 GMT+0200",
+          "dam:ApertureValue": 4.64385986328125,
+          "dc:format": "image/jpeg",
+          "dam:Bitsperpixel": 24,
+          "exif:FileSource": 3,
+          "dam:OtherImageLength": 2788,
+          "exif:ExifVersion": "0221",
+          "tiff:YCbCrPositioning": 1,
+          "dam:MIMEtype": "image/jpeg",
+          "tiff:Orientation": 1,
+          "dam:FocalPlaneXResolution": 3443.946188340807,
+          "dam:JpgFromRawLength": 2788,
+          "dam:Physicalwidthindpi": 180,
+          "dam:Physicalheightindpi": 180,
+          "exif:Flash": 0,
+          "exif:CompressedBitsPerPixel": 3,
+          "tiff:ResolutionUnit": 2,
+          "dam:ShutterSpeedValue": 5.906890869140625,
+          "exif:ComponentsConfiguration": ["[B@d19c55"],
+          "dam:FocalPlaneResolutionUnit": 2,
+          "dc:modified": "Wed Oct 06 2010 15:53:23 GMT+0200",
+          "dam:Numberofimages": 1,
+          "exif:ExposureMode": 0,
+          "exif:ExposureTime": 0.016666666666666666,
+          "tiff:XResolution": 180,
+          "dam:ExifOffset": 220,
+          "dam:PreviewImageStart": 1054,
+          "exif:WhiteBalance": 0,
+          "app:tags": [
+            "stockphotography:business/business_people",
+            "properties:style/color",
+            "properties:orientation/landscape"
+          ],
+          "jcr:lastModified": "Wed May 08 2013 10:21:57 GMT+0200",
+          "dam:Numberoftextualcomments": 0,
+          "xmp:ModifyDate": "Sat Oct 01 2005 15:16:30 GMT+0200",
+          "tiff:ImageWidth": 807,
+          "exif:DateTimeOriginal": "Sat Oct 01 2005 15:16:30 GMT+0200",
+          "dam:ModifyDate": "Thu Oct 20 2005 12:56:51 GMT+0200",
+          "tiff:Model": "Canon EOS 300D DIGITAL\u0000",
+          "exif:MeteringMode": 5,
+          "exif:SceneCaptureType": 0,
+          "dc:title": "Scott Reynolds",
+          "dam:PreviewImageLength": 2788,
+          "dam:FocalPlaneYResolution": 3442.016806722689
+        }
+      }
+    }
+  }
+}
diff --git a/src/test/resources/sample-image.gif b/src/test/resources/sample-image.gif
new file mode 100644
index 0000000..8b310f6
Binary files /dev/null and b/src/test/resources/sample-image.gif differ

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