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:18:23 UTC

[sling-org-apache-sling-testing-osgi-mock] 01/23: 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.osgi-mock-1.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-osgi-mock.git

commit e5ba840d79da4de23084251e3d5ce06e04c5973a
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/osgi-mock@1631356 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            | 119 ++++++++
 .../apache/sling/testing/mock/osgi/MockBundle.java | 182 +++++++++++++
 .../sling/testing/mock/osgi/MockBundleContext.java | 220 +++++++++++++++
 .../testing/mock/osgi/MockComponentContext.java    |  98 +++++++
 .../apache/sling/testing/mock/osgi/MockFilter.java |  46 ++++
 .../sling/testing/mock/osgi/MockLogService.java    |  87 ++++++
 .../apache/sling/testing/mock/osgi/MockOsgi.java   | 250 +++++++++++++++++
 .../testing/mock/osgi/MockServiceReference.java    | 131 +++++++++
 .../testing/mock/osgi/MockServiceRegistration.java | 105 +++++++
 .../sling/testing/mock/osgi/OsgiMetadataUtil.java  | 278 +++++++++++++++++++
 .../testing/mock/osgi/ReflectionServiceUtil.java   | 301 +++++++++++++++++++++
 src/main/resources/simplelogger.properties         |  18 ++
 src/site/markdown/index.md                         |  35 +++
 src/site/markdown/usage.md                         |  59 ++++
 .../testing/mock/osgi/MockBundleContextTest.java   | 159 +++++++++++
 .../sling/testing/mock/osgi/MockBundleTest.java    |  60 ++++
 .../mock/osgi/MockComponentContextTest.java        |  84 ++++++
 .../sling/testing/mock/osgi/MockFilterTest.java    |  46 ++++
 .../testing/mock/osgi/MockLogServiceTest.java      |  70 +++++
 .../mock/osgi/MockServiceReferenceTest.java        |  81 ++++++
 .../testing/mock/osgi/OsgiMetadataUtilTest.java    |  93 +++++++
 .../mock/osgi/ReflectionServiceUtilTest.java       | 226 ++++++++++++++++
 .../sling/testing/mock/osgi/package-info.java      |  23 ++
 src/test/resources/META-INF/test.txt               |   1 +
 ...ling.testing.mock.osgi.OsgiMetadataUtilTest.xml |  14 +
 ...testing.mock.osgi.ReflectionServiceUtilTest.xml |  32 +++
 src/test/resources/simplelogger.properties         |  18 ++
 27 files changed, 2836 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..708d885
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,119 @@
+<?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.osgi-mock</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Sling Testing OSGi Mock</name>
+    <description>Mock implementation of selected OSGi APIs.</description>
+
+    <properties>
+        <sling.java.version>6</sling.java.version>
+    </properties>
+
+    <dependencies>
+  
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>15.0</version>
+            <scope>compile</scope>
+        </dependency>
+  
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.0.1</version>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>compile</scope>
+        </dependency>
+    
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+  
+    <build>
+        <plugins>
+    
+            <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/osgi/MockBundle.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockBundle.java
new file mode 100644
index 0000000..6c1f22e
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockBundle.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.osgi;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+
+/**
+ * Mock {@link Bundle} implementation.
+ */
+class MockBundle implements Bundle {
+
+    private static volatile long bundleCounter;
+
+    private final long bundleId;
+    private final BundleContext bundleContext;
+
+    /**
+     * Constructor
+     */
+    public MockBundle(BundleContext bundleContext) {
+        this.bundleId = ++bundleCounter;
+        this.bundleContext = bundleContext;
+    }
+
+    @Override
+    public long getBundleId() {
+        return this.bundleId;
+    }
+
+    @Override
+    public BundleContext getBundleContext() {
+        return this.bundleContext;
+    }
+
+    @Override
+    public URL getEntry(final String name) {
+        // try to load resource from classpath
+        return getClass().getResource(name);
+    }
+
+    @Override
+    public int getState() {
+        return Bundle.ACTIVE;
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public Enumeration<?> findEntries(final String path, final String filePattern, final boolean recurse) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<?> getEntryPaths(final String path) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Dictionary<?, ?> getHeaders() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Dictionary<?, ?> getHeaders(final String locale) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long getLastModified() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getLocation() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServiceReference[] getRegisteredServices() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public URL getResource(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Enumeration<?> getResources(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServiceReference[] getServicesInUse() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getSymbolicName() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasPermission(final Object permission) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Class<?> loadClass(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void start() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void stop() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void uninstall() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void update() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void update(final InputStream inputStream) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void start(final int options) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void stop(final int options) {
+        throw new UnsupportedOperationException();
+    }
+
+    // this is part of org.osgi 4.2.0
+    public Map getSignerCertificates(final int signersType) {
+        throw new UnsupportedOperationException();
+    }
+
+    // this is part of org.osgi 4.2.0
+    public Version getVersion() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java
new file mode 100644
index 0000000..cbaa13d
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockBundleContext.java
@@ -0,0 +1,220 @@
+/*
+ * 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.osgi;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.lang3.StringUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Mock {@link BundleContext} implementation.
+ */
+class MockBundleContext implements BundleContext {
+
+    private final MockBundle bundle;
+    private final List<MockServiceRegistration> registeredServices = new ArrayList<MockServiceRegistration>();
+    private final List<ServiceListener> serviceListeners = new ArrayList<ServiceListener>();
+    private final List<BundleListener> bundleListeners = new ArrayList<BundleListener>();
+
+    public MockBundleContext() {
+        this.bundle = new MockBundle(this);
+    }
+
+    @Override
+    public Bundle getBundle() {
+        return this.bundle;
+    }
+
+    @Override
+    public Filter createFilter(final String s) {
+        // return filter that denies all
+        return new MockFilter();
+    }
+
+    @Override
+    public ServiceRegistration registerService(final String clazz, final Object service, final Dictionary properties) {
+        String[] clazzes;
+        if (StringUtils.isBlank(clazz)) {
+            clazzes = new String[0];
+        } else {
+            clazzes = new String[] { clazz };
+        }
+        return registerService(clazzes, service, properties);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public ServiceRegistration registerService(final String[] clazzes, final Object service, final Dictionary properties) {
+        MockServiceRegistration registration = new MockServiceRegistration(this.bundle, clazzes, service, properties);
+        this.registeredServices.add(registration);
+        notifyServiceListeners(ServiceEvent.REGISTERED, registration.getReference());
+        return registration;
+    }
+
+    @Override
+    public ServiceReference getServiceReference(final String clazz) {
+        ServiceReference[] serviceRefs = getServiceReferences(clazz, null);
+        if (serviceRefs != null && serviceRefs.length > 0) {
+            return serviceRefs[0];
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public ServiceReference[] getServiceReferences(final String clazz, final String filter) {
+        Set<ServiceReference> result = new TreeSet<ServiceReference>();
+        for (MockServiceRegistration serviceRegistration : this.registeredServices) {
+            if (serviceRegistration.matches(clazz, filter)) {
+                result.add(serviceRegistration.getReference());
+            }
+        }
+        if (result.isEmpty()) {
+            return null;
+        } else {
+            return result.toArray(new ServiceReference[result.size()]);
+        }
+    }
+
+    @Override
+    public ServiceReference[] getAllServiceReferences(final String clazz, final String filter) {
+        // for now just do the same as getServiceReferences
+        return getServiceReferences(clazz, filter);
+    }
+
+    @Override
+    public Object getService(final ServiceReference serviceReference) {
+        return ((MockServiceReference) serviceReference).getService();
+    }
+
+    @Override
+    public boolean ungetService(final ServiceReference serviceReference) {
+        // do nothing for now
+        return false;
+    }
+
+    @Override
+    public void addServiceListener(final ServiceListener serviceListener) {
+        addServiceListener(serviceListener, null);
+    }
+
+    @Override
+    public void addServiceListener(final ServiceListener serviceListener, final String s) {
+        if (!serviceListeners.contains(serviceListener)) {
+            serviceListeners.add(serviceListener);
+        }
+    }
+
+    @Override
+    public void removeServiceListener(final ServiceListener serviceListener) {
+        serviceListeners.remove(serviceListener);
+    }
+
+    private void notifyServiceListeners(int eventType, ServiceReference serviceReference) {
+        final ServiceEvent event = new ServiceEvent(eventType, serviceReference);
+        for (ServiceListener serviceListener : serviceListeners) {
+            serviceListener.serviceChanged(event);
+        }
+    }
+
+    @Override
+    public void addBundleListener(final BundleListener bundleListener) {
+        if (!bundleListeners.contains(bundleListener)) {
+            bundleListeners.add(bundleListener);
+        }
+    }
+
+    @Override
+    public void removeBundleListener(final BundleListener bundleListener) {
+        bundleListeners.remove(bundleListener);
+    }
+
+    void sendBundleEvent(BundleEvent bundleEvent) {
+        for (BundleListener bundleListener : bundleListeners) {
+            bundleListener.bundleChanged(bundleEvent);
+        }
+    }
+
+    @Override
+    public void addFrameworkListener(final FrameworkListener frameworkListener) {
+        // accept method, but ignore it
+    }
+
+    @Override
+    public void removeFrameworkListener(final FrameworkListener frameworkListener) {
+        // accept method, but ignore it
+    }
+
+    Object locateService(final String name, final ServiceReference reference) {
+        for (MockServiceRegistration serviceRegistration : this.registeredServices) {
+            if (serviceRegistration.getReference() == reference) {
+                return serviceRegistration.getService();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Bundle[] getBundles() {
+        return new Bundle[0];
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public String getProperty(final String s) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Bundle installBundle(final String s) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Bundle installBundle(final String s, final InputStream inputStream) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Bundle getBundle(final long l) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public File getDataFile(final String s) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockComponentContext.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockComponentContext.java
new file mode 100644
index 0000000..cf70b1d
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockComponentContext.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.osgi;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.ComponentInstance;
+
+/**
+ * Mock {@link ComponentContext} implementation.
+ */
+class MockComponentContext implements ComponentContext {
+
+    private final MockBundleContext bundleContext;
+    private final Dictionary<String, Object> properties;
+
+    public MockComponentContext(final MockBundleContext mockBundleContext) {
+        this(mockBundleContext, new Hashtable<String, Object>());
+    }
+
+    public MockComponentContext(final MockBundleContext mockBundleContext, final Dictionary<String, Object> properties) {
+        this.bundleContext = mockBundleContext;
+        this.properties = properties;
+    }
+
+    @Override
+    public Dictionary<String, Object> getProperties() {
+        return this.properties;
+    }
+
+    @Override
+    public Object locateService(final String name, final ServiceReference reference) {
+        return this.bundleContext.locateService(name, reference);
+    }
+
+    @Override
+    public BundleContext getBundleContext() {
+        return this.bundleContext;
+    }
+
+    @Override
+    public void disableComponent(final String name) {
+        // allow calling, but ignore
+    }
+
+    @Override
+    public void enableComponent(final String name) {
+        // allow calling, but ignore
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public ComponentInstance getComponentInstance() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ServiceReference getServiceReference() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Bundle getUsingBundle() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Object locateService(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Object[] locateServices(final String name) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockFilter.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockFilter.java
new file mode 100644
index 0000000..b0f585c
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockFilter.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.osgi;
+
+import java.util.Dictionary;
+
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Mock {@link Filter} implementation.
+ */
+class MockFilter implements Filter {
+
+    @Override
+    public boolean match(final ServiceReference reference) {
+        return false;
+    }
+
+    @Override
+    public boolean match(final Dictionary dictionary) {
+        return false;
+    }
+
+    @Override
+    public boolean matchCase(final Dictionary dictionary) {
+        return false;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockLogService.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockLogService.java
new file mode 100644
index 0000000..28d1433
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockLogService.java
@@ -0,0 +1,87 @@
+/*
+ * 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.osgi;
+
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Mock {@link LogService} implementation.
+ */
+class MockLogService implements LogService {
+
+    private final Logger log;
+
+    public MockLogService(final Class<?> loggerContext) {
+        this.log = LoggerFactory.getLogger(loggerContext);
+    }
+
+    @Override
+    public void log(final int level, final String message) {
+        switch (level) {
+        case LogService.LOG_ERROR:
+            this.log.error(message);
+            break;
+        case LogService.LOG_WARNING:
+            this.log.warn(message);
+            break;
+        case LogService.LOG_INFO:
+            this.log.info(message);
+            break;
+        case LogService.LOG_DEBUG:
+            this.log.debug(message);
+            break;
+        default:
+            throw new IllegalArgumentException("Invalid log level: " + level);
+        }
+    }
+
+    @Override
+    public void log(final int level, final String message, final Throwable exception) {
+        switch (level) {
+        case LogService.LOG_ERROR:
+            this.log.error(message, exception);
+            break;
+        case LogService.LOG_WARNING:
+            this.log.warn(message, exception);
+            break;
+        case LogService.LOG_INFO:
+            this.log.info(message, exception);
+            break;
+        case LogService.LOG_DEBUG:
+            this.log.debug(message, exception);
+            break;
+        default:
+            throw new IllegalArgumentException("Invalid log level: " + level);
+        }
+    }
+
+    @Override
+    public void log(final ServiceReference sr, final int level, final String message) {
+        log(level, message);
+    }
+
+    @Override
+    public void log(final ServiceReference sr, final int level, final String message, final Throwable exception) {
+        log(level, message, exception);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockOsgi.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockOsgi.java
new file mode 100644
index 0000000..fd65fe1
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockOsgi.java
@@ -0,0 +1,250 @@
+/*
+ * 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.osgi;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.log.LogService;
+
+/**
+ * Factory for mock OSGi objects.
+ */
+public final class MockOsgi {
+
+    private MockOsgi() {
+        // static methods only
+    }
+
+    /**
+     * @return Mocked {@link BundleContext} instance
+     */
+    public static BundleContext newBundleContext() {
+        return new MockBundleContext();
+    }
+
+    /**
+     * Simulates a bundle event on the given bundle context (that is forwarded
+     * to registered bundle listeners).
+     * @param bundleContext Bundle context
+     * @param bundleEvent Bundle event
+     */
+    public static void sendBundleEvent(BundleContext bundleContext, BundleEvent bundleEvent) {
+        ((MockBundleContext) bundleContext).sendBundleEvent(bundleEvent);
+    }
+
+    /**
+     * @return Mocked {@link ComponentContext} instance
+     */
+    public static ComponentContext newComponentContext() {
+        return new MockComponentContext((MockBundleContext) newBundleContext());
+    }
+
+    /**
+     * @param properties Properties
+     * @return Mocked {@link ComponentContext} instance
+     */
+    public static ComponentContext newComponentContext(Dictionary<String, Object> properties) {
+        return newComponentContext(newBundleContext(), properties);
+    }
+
+    /**
+     * @param properties Properties
+     * @return Mocked {@link ComponentContext} instance
+     */
+    public static ComponentContext newComponentContext(Map<String, Object> properties) {
+        return newComponentContext(toDictionary(properties));
+    }
+
+    /**
+     * @param bundleContext Bundle context
+     * @param properties Properties
+     * @return Mocked {@link ComponentContext} instance
+     */
+    public static ComponentContext newComponentContext(BundleContext bundleContext,
+            Dictionary<String, Object> properties) {
+        return new MockComponentContext((MockBundleContext) bundleContext, properties);
+    }
+
+    /**
+     * @param bundleContext Bundle context
+     * @param properties Properties
+     * @return Mocked {@link ComponentContext} instance
+     */
+    public static ComponentContext newComponentContext(BundleContext bundleContext, Map<String, Object> properties) {
+        return newComponentContext(bundleContext, toDictionary(properties));
+    }
+
+    /**
+     * @param loggerContext Context class for logging
+     * @return Mocked {@link LogService} instance
+     */
+    public static LogService newLogService(final Class<?> loggerContext) {
+        return new MockLogService(loggerContext);
+    }
+
+    /**
+     * Simulate OSGi service dependency injection. Injects direct references and
+     * multiple references. If a some references could not be injected no error
+     * is thrown.
+     * @param target Service instance
+     * @param bundleContext Bundle context from which services are fetched to inject.
+     * @return true if all dependencies could be injected
+     */
+    public static boolean injectServices(Object target, BundleContext bundleContext) {
+        return ReflectionServiceUtil.injectServices(target, bundleContext);
+    }
+
+    /**
+     * Simulate activation of service instance. Invokes the @Activate annotated
+     * method.
+     * @param target Service instance.
+     * @return true if activation method was called. False if such a method did
+     *         not exist.
+     */
+    public static boolean activate(Object target) {
+        ComponentContext componentContext = newComponentContext();
+        return ReflectionServiceUtil.activateDeactivate(target, componentContext, true);
+    }
+
+    /**
+     * Simulate activation of service instance. Invokes the @Activate annotated
+     * method.
+     * @param target Service instance.
+     * @param properties Properties
+     * @return true if activation method was called. False if such a method did
+     *         not exist.
+     */
+    public static boolean activate(Object target, Dictionary<String, Object> properties) {
+        ComponentContext componentContext = newComponentContext(properties);
+        return ReflectionServiceUtil.activateDeactivate(target, componentContext, true);
+    }
+
+    /**
+     * Simulate activation of service instance. Invokes the @Activate annotated
+     * method.
+     * @param target Service instance.
+     * @param properties Properties
+     * @return true if activation method was called. False if such a method did
+     *         not exist.
+     */
+    public static boolean activate(Object target, Map<String, Object> properties) {
+        return activate(target, toDictionary(properties));
+    }
+
+    /**
+     * Simulate activation of service instance. Invokes the @Activate annotated
+     * method.
+     * @param target Service instance.
+     * @param bundleContext Bundle context
+     * @param properties Properties
+     * @return true if activation method was called. False if such a method did
+     *         not exist.
+     */
+    public static boolean activate(Object target, BundleContext bundleContext, Dictionary<String, Object> properties) {
+        ComponentContext componentContext = newComponentContext(bundleContext, properties);
+        return ReflectionServiceUtil.activateDeactivate(target, componentContext, true);
+    }
+
+    /**
+     * Simulate activation of service instance. Invokes the @Activate annotated
+     * method.
+     * @param target Service instance.
+     * @param bundleContext Bundle context
+     * @param properties Properties
+     * @return true if activation method was called. False if such a method did
+     *         not exist.
+     */
+    public static boolean activate(Object target, BundleContext bundleContext, Map<String, Object> properties) {
+        return activate(target, bundleContext, toDictionary(properties));
+    }
+
+    /**
+     * Simulate deactivation of service instance. Invokes the @Activate
+     * annotated method.
+     * @param target Service instance.
+     * @return true if deactivation method was called. False if such a method
+     *         did not exist.
+     */
+    public static boolean deactivate(Object target) {
+        ComponentContext componentContext = newComponentContext();
+        return ReflectionServiceUtil.activateDeactivate(target, componentContext, false);
+    }
+
+    /**
+     * Simulate deactivation of service instance. Invokes the @Activate
+     * annotated method.
+     * @param target Service instance.
+     * @param properties Properties
+     * @return true if deactivation method was called. False if such a method
+     *         did not exist.
+     */
+    public static boolean deactivate(Object target, Dictionary<String, Object> properties) {
+        ComponentContext componentContext = newComponentContext(properties);
+        return ReflectionServiceUtil.activateDeactivate(target, componentContext, false);
+    }
+
+    /**
+     * Simulate deactivation of service instance. Invokes the @Activate
+     * annotated method.
+     * @param target Service instance.
+     * @param properties Properties
+     * @return true if deactivation method was called. False if such a method
+     *         did not exist.
+     */
+    public static boolean deactivate(Object target, Map<String, Object> properties) {
+        return deactivate(target, toDictionary(properties));
+    }
+
+    /**
+     * Simulate deactivation of service instance. Invokes the @Activate
+     * annotated method.
+     * @param target Service instance.
+     * @param bundleContext Bundle context
+     * @param properties Properties
+     * @return true if deactivation method was called. False if such a method
+     *         did not exist.
+     */
+    public static boolean deactivate(Object target, BundleContext bundleContext, Dictionary<String, Object> properties) {
+        ComponentContext componentContext = newComponentContext(bundleContext, properties);
+        return ReflectionServiceUtil.activateDeactivate(target, componentContext, false);
+    }
+
+    /**
+     * Simulate activation of service instance. Invokes the @Activate annotated
+     * method.
+     * @param target Service instance.
+     * @param bundleContext Bundle context
+     * @param properties Properties
+     * @return true if activation method was called. False if such a method did
+     *         not exist.
+     */
+    public static boolean deactivate(Object target, BundleContext bundleContext, Map<String, Object> properties) {
+        return deactivate(target, bundleContext, toDictionary(properties));
+    }
+
+    private static Dictionary<String, Object> toDictionary(Map<String, Object> map) {
+        return new Hashtable<String, Object>(map);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockServiceReference.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockServiceReference.java
new file mode 100644
index 0000000..c7568be
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockServiceReference.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.osgi;
+
+import java.util.Collections;
+import java.util.Dictionary;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Mock {@link ServiceReference} implementation.
+ */
+class MockServiceReference implements ServiceReference {
+
+    private final Bundle bundle;
+    private final MockServiceRegistration serviceRegistration;
+
+    public MockServiceReference(final Bundle bundle, final MockServiceRegistration serviceRegistration) {
+        this.bundle = bundle;
+        this.serviceRegistration = serviceRegistration;
+    }
+
+    @Override
+    public Bundle getBundle() {
+        return this.bundle;
+    }
+
+    /**
+     * Set service reference property
+     * @param key Key
+     * @param value Value
+     */
+    public void setProperty(final String key, final Object value) {
+        this.serviceRegistration.getProperties().put(key, value);
+    }
+
+    @Override
+    public Object getProperty(final String key) {
+        return this.serviceRegistration.getProperties().get(key);
+    }
+
+    @Override
+    public String[] getPropertyKeys() {
+        Dictionary<String, Object> props = this.serviceRegistration.getProperties();
+        return Collections.list(props.keys()).toArray(new String[props.size()]);
+    }
+
+    @Override
+    public int hashCode() {
+        return ((Long) getServiceId()).hashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (!(obj instanceof MockServiceReference)) {
+            return false;
+        }
+        return ((Long) getServiceId()).equals(((MockServiceReference) obj).getServiceId());
+    }
+
+    @Override
+    public int compareTo(final Object obj) {
+        if (!(obj instanceof MockServiceReference)) {
+            return 0;
+        }
+        // sort by decreasing by service ranking, and secondary increasing by
+        // service id
+        Integer serviceRanking = getServiceRanking();
+        Integer otherServiceRanking = ((MockServiceReference) obj).getServiceRanking();
+        int serviceRankingCompare = otherServiceRanking.compareTo(serviceRanking);
+        if (serviceRankingCompare == 0) {
+            Long serviceId = getServiceId();
+            Long otherServiceId = ((MockServiceReference) obj).getServiceId();
+            return serviceId.compareTo(otherServiceId);
+        } else {
+            return serviceRankingCompare;
+        }
+    }
+
+    long getServiceId() {
+        Number serviceID = (Number) getProperty(Constants.SERVICE_ID);
+        if (serviceID != null) {
+            return serviceID.longValue();
+        } else {
+            return 0L;
+        }
+    }
+
+    int getServiceRanking() {
+        Number serviceRanking = (Number) getProperty(Constants.SERVICE_RANKING);
+        if (serviceRanking != null) {
+            return serviceRanking.intValue();
+        } else {
+            return 0;
+        }
+    }
+
+    Object getService() {
+        return this.serviceRegistration.getService();
+    }
+
+    // --- unsupported operations ---
+    @Override
+    public Bundle[] getUsingBundles() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isAssignableTo(final Bundle otherBundle, final String className) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/MockServiceRegistration.java b/src/main/java/org/apache/sling/testing/mock/osgi/MockServiceRegistration.java
new file mode 100644
index 0000000..a3bdaf5
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/MockServiceRegistration.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.testing.mock.osgi;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.w3c.dom.Document;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Mock {@link ServiceRegistration} implementation.
+ */
+class MockServiceRegistration implements ServiceRegistration {
+
+    private static volatile long serviceCounter;
+
+    private final Set<String> clazzes;
+    private final Object service;
+    private Dictionary<String, Object> properties;
+    private final ServiceReference serviceReference;
+
+    @SuppressWarnings("unchecked")
+    public MockServiceRegistration(final Bundle bundle, final String[] clazzes, final Object service,
+            final Dictionary<String, Object> properties) {
+        this.clazzes = new HashSet<String>(ImmutableList.copyOf(clazzes));
+        this.service = service;
+        this.properties = properties != null ? properties : new Hashtable();
+        this.properties.put(Constants.SERVICE_ID, ++serviceCounter);
+        this.serviceReference = new MockServiceReference(bundle, this);
+        readOsgiMetadata();
+    }
+
+    @Override
+    public ServiceReference getReference() {
+        return this.serviceReference;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void setProperties(final Dictionary properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public void unregister() {
+        // do nothing for now
+    }
+
+    Dictionary<String, Object> getProperties() {
+        return this.properties;
+    }
+
+    boolean matches(final String clazz, final String filter) {
+        // ignore filter for now
+        return this.clazzes.contains(clazz);
+    }
+
+    Object getService() {
+        return this.service;
+    }
+
+    /**
+     * Try to read OSGI-metadata from /OSGI-INF and read all implemented
+     * interfaces and service properties
+     */
+    private void readOsgiMetadata() {
+        Class<?> serviceClass = service.getClass();
+        Document doc = OsgiMetadataUtil.getMetadata(serviceClass);
+
+        // add service interfaces from OSGi metadata
+        clazzes.addAll(OsgiMetadataUtil.getServiceInterfaces(serviceClass, doc));
+
+        // add properties from OSGi metadata
+        Map<String, Object> props = OsgiMetadataUtil.getProperties(serviceClass, doc);
+        for (Map.Entry<String, Object> entry : props.entrySet()) {
+            properties.put(entry.getKey(), entry.getValue());
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java b/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java
new file mode 100644
index 0000000..da8bca0
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtil.java
@@ -0,0 +1,278 @@
+/*
+ * 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.osgi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+
+/**
+ * Helper methods to parse OSGi metadata.
+ */
+final class OsgiMetadataUtil {
+
+    private static final Logger log = LoggerFactory.getLogger(OsgiMetadataUtil.class);
+
+    private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY;
+    static {
+        DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
+        DOCUMENT_BUILDER_FACTORY.setNamespaceAware(true);
+    }
+
+    private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance();
+
+    private static final BiMap<String, String> NAMESPACES = HashBiMap.create();
+    static {
+        NAMESPACES.put("scr", "http://www.osgi.org/xmlns/scr/v1.1.0");
+    }
+
+    private OsgiMetadataUtil() {
+        // static methods only
+    }
+
+    private static final NamespaceContext NAMESPACE_CONTEXT = new NamespaceContext() {
+        @Override
+        public String getNamespaceURI(String prefix) {
+            return NAMESPACES.get(prefix);
+        }
+
+        @Override
+        public String getPrefix(String namespaceURI) {
+            return NAMESPACES.inverse().get(namespaceURI);
+        }
+
+        @Override
+        public Iterator getPrefixes(String namespaceURI) {
+            return NAMESPACES.keySet().iterator();
+        }
+    };
+
+    /**
+     * Try to read OSGI-metadata from /OSGI-INF and read all implemented
+     * interfaces and service properties
+     * @param clazz OSGi service implementation class
+     * @return Metadata document or null
+     */
+    public static Document getMetadata(Class clazz) {
+        String metadataPath = "/OSGI-INF/" + StringUtils.substringBefore(clazz.getName(), "$") + ".xml";
+        InputStream metadataStream = clazz.getResourceAsStream(metadataPath);
+        if (metadataStream == null) {
+            log.debug("No OSGi metadata found at {}", metadataPath);
+            return null;
+        }
+        try {
+            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+            return documentBuilder.parse(metadataStream);
+        } catch (ParserConfigurationException ex) {
+            throw new RuntimeException("Unable to read classpath resource: " + metadataPath, ex);
+        } catch (SAXException ex) {
+            throw new RuntimeException("Unable to read classpath resource: " + metadataPath, ex);
+        } catch (IOException ex) {
+            throw new RuntimeException("Unable to read classpath resource: " + metadataPath, ex);
+        } finally {
+            try {
+                metadataStream.close();
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+    }
+
+    public static Set<String> getServiceInterfaces(Class clazz, Document metadata) {
+        Set<String> serviceInterfaces = new HashSet<String>();
+        if (metadata != null) {
+            String query = "/components/component[@name='" + clazz.getName() + "']/service/provide[@interface!='']";
+            NodeList nodes = queryNodes(metadata, query);
+            if (nodes != null) {
+                for (int i = 0; i < nodes.getLength(); i++) {
+                    Node node = nodes.item(i);
+                    String serviceInterface = getAttributeValue(node, "interface");
+                    if (StringUtils.isNotBlank(serviceInterface)) {
+                        serviceInterfaces.add(serviceInterface);
+                    }
+                }
+            }
+        }
+        return serviceInterfaces;
+    }
+
+    public static Map<String, Object> getProperties(Class clazz, Document metadata) {
+        Map<String, Object> props = new HashMap<String, Object>();
+        if (metadata != null) {
+            String query = "/components/component[@name='" + clazz.getName() + "']/property[@name!='' and @value!='']";
+            NodeList nodes = queryNodes(metadata, query);
+            if (nodes != null) {
+                for (int i = 0; i < nodes.getLength(); i++) {
+                    Node node = nodes.item(i);
+                    String name = getAttributeValue(node, "name");
+                    String value = getAttributeValue(node, "value");
+                    String type = getAttributeValue(node, "type");
+                    if (StringUtils.equals("Integer", type)) {
+                        props.put(name, Integer.parseInt(value));
+                    } else {
+                        props.put(name, value);
+                    }
+                }
+            }
+        }
+        return props;
+    }
+
+    public static List<Reference> getReferences(Class clazz, Document metadata) {
+        List<Reference> references = new ArrayList<Reference>();
+        if (metadata != null) {
+            String query = "/components/component[@name='" + clazz.getName() + "']/reference[@name!='']";
+            NodeList nodes = queryNodes(metadata, query);
+            if (nodes != null) {
+                for (int i = 0; i < nodes.getLength(); i++) {
+                    Node node = nodes.item(i);
+                    references.add(new Reference(node));
+                }
+            }
+        }
+        return references;
+    }
+
+    public static String getActivateMethodName(Class clazz, Document metadata) {
+        if (metadata != null) {
+            String query = "/components/component[@name='" + clazz.getName() + "']";
+            Node node = queryNode(metadata, query);
+            if (node != null) {
+                return getAttributeValue(node, "activate");
+            }
+        }
+        return null;
+    }
+
+    public static String getDeactivateMethodName(Class clazz, Document metadata) {
+        if (metadata != null) {
+            String query = "/components/component[@name='" + clazz.getName() + "']";
+            Node node = queryNode(metadata, query);
+            if (node != null) {
+                return getAttributeValue(node, "deactivate");
+            }
+        }
+        return null;
+    }
+
+    private static NodeList queryNodes(Document metadata, String xpathQuery) {
+        try {
+            XPath xpath = XPATH_FACTORY.newXPath();
+            xpath.setNamespaceContext(NAMESPACE_CONTEXT);
+            return (NodeList) xpath.evaluate(xpathQuery, metadata, XPathConstants.NODESET);
+        } catch (XPathExpressionException ex) {
+            throw new RuntimeException("Error evaluating XPath: " + xpathQuery, ex);
+        }
+    }
+
+    private static Node queryNode(Document metadata, String xpathQuery) {
+        try {
+            XPath xpath = XPATH_FACTORY.newXPath();
+            xpath.setNamespaceContext(NAMESPACE_CONTEXT);
+            return (Node) xpath.evaluate(xpathQuery, metadata, XPathConstants.NODE);
+        } catch (XPathExpressionException ex) {
+            throw new RuntimeException("Error evaluating XPath: " + xpathQuery, ex);
+        }
+    }
+
+    private static String getAttributeValue(Node node, String attributeName) {
+        Node namedItem = node.getAttributes().getNamedItem(attributeName);
+        if (namedItem != null) {
+            return namedItem.getNodeValue();
+        } else {
+            return null;
+        }
+    }
+
+    public static class Reference {
+
+        private final String name;
+        private final String interfaceType;
+        private final ReferenceCardinality cardinality;
+        private final String bind;
+        private final String unbind;
+
+        public Reference(Node node) {
+            this.name = getAttributeValue(node, "name");
+            this.interfaceType = getAttributeValue(node, "interface");
+            this.cardinality = toCardinality(getAttributeValue(node, "cardinality"));
+            this.bind = getAttributeValue(node, "bind");
+            this.unbind = getAttributeValue(node, "unbind");
+        }
+
+        private ReferenceCardinality toCardinality(String value) {
+            for (ReferenceCardinality item : ReferenceCardinality.values()) {
+                if (StringUtils.equals(item.getCardinalityString(), value)) {
+                    return item;
+                }
+            }
+            return ReferenceCardinality.MANDATORY_UNARY;
+        }
+
+        public String getName() {
+            return this.name;
+        }
+
+        public String getInterfaceType() {
+            return this.interfaceType;
+        }
+
+        public ReferenceCardinality getCardinality() {
+            return this.cardinality;
+        }
+
+        public String getBind() {
+            return this.bind;
+        }
+
+        public String getUnbind() {
+            return this.unbind;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/testing/mock/osgi/ReflectionServiceUtil.java b/src/main/java/org/apache/sling/testing/mock/osgi/ReflectionServiceUtil.java
new file mode 100644
index 0000000..aa338a3
--- /dev/null
+++ b/src/main/java/org/apache/sling/testing/mock/osgi/ReflectionServiceUtil.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.osgi;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.Reference;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+
+/**
+ * Helper methods to inject dependencies and activate services via reflection.
+ */
+final class ReflectionServiceUtil {
+
+    private static final Logger log = LoggerFactory.getLogger(ReflectionServiceUtil.class);
+
+    private ReflectionServiceUtil() {
+        // static methods only
+    }
+
+    /**
+     * Simulate activation or deactivation of OSGi service instance.
+     * @param target Service instance.
+     * @param componentContext Component context
+     * @return true if activation method was called. False if such a method did
+     *         not exist.
+     */
+    public static boolean activateDeactivate(Object target, ComponentContext componentContext, boolean activate) {
+        Class<?> targetClass = target.getClass();
+
+        // get method name for activation/deactivation from osgi metadata
+        Document metadata = OsgiMetadataUtil.getMetadata(targetClass);
+        String methodName;
+        if (activate) {
+            methodName = OsgiMetadataUtil.getActivateMethodName(targetClass, metadata);
+        } else {
+            methodName = OsgiMetadataUtil.getDeactivateMethodName(targetClass, metadata);
+        }
+        if (StringUtils.isEmpty(methodName)) {
+            return false;
+        }
+
+        // if method is defined try to execute it
+        Method method = getMethod(targetClass, methodName, new Class<?>[] { ComponentContext.class }, activate);
+        if (method != null) {
+            try {
+                method.setAccessible(true);
+                method.invoke(target, componentContext);
+                return true;
+            } catch (IllegalAccessException ex) {
+                throw new RuntimeException("Unable to invoke activate/deactivate method for class "
+                        + targetClass.getName(), ex);
+            } catch (IllegalArgumentException ex) {
+                throw new RuntimeException("Unable to invoke activate/deactivate method for class "
+                        + targetClass.getName(), ex);
+            } catch (InvocationTargetException ex) {
+                throw new RuntimeException("Unable to invoke activate/deactivate method for class "
+                        + targetClass.getName(), ex);
+            }
+        }
+        log.warn("Method {}(ComponentContext) not found in class {}", methodName, targetClass.getName());
+        return false;
+    }
+
+    private static Method getMethod(Class clazz, String methodName, Class<?>[] signature, boolean activate) {
+        Method[] methods = clazz.getDeclaredMethods();
+        for (Method method : methods) {
+            if (StringUtils.equals(method.getName(), methodName)
+                    && Arrays.equals(method.getParameterTypes(), signature)) {
+                return method;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Simulate OSGi service dependency injection. Injects direct references and
+     * multiple references.
+     * @param target Service instance
+     * @param bundleContext Bundle context from which services are fetched to inject.
+     * @return true if all dependencies could be injected
+     */
+    public static boolean injectServices(Object target, BundleContext bundleContext) {
+
+        // collect all declared reference annotations on class and field level
+        Class<?> targetClass = target.getClass();
+        List<Reference> references = getReferences(targetClass);
+
+        // try to inject services
+        boolean allInjected = true;
+        for (Reference reference : references) {
+            allInjected = allInjected && injectServiceReference(reference, target, bundleContext);
+        }
+        return allInjected;
+    }
+
+    private static List<Reference> getReferences(Class clazz) {
+        Document metadata = OsgiMetadataUtil.getMetadata(clazz);
+        return OsgiMetadataUtil.getReferences(clazz, metadata);
+    }
+
+    private static boolean injectServiceReference(Reference reference, Object target, BundleContext bundleContext) {
+        Class<?> targetClass = target.getClass();
+
+        // get reference type
+        Class<?> type;
+        try {
+            type = Class.forName(reference.getInterfaceType());
+        } catch (ClassNotFoundException ex) {
+            throw new RuntimeException("Unable to instantiate reference type: " + reference.getInterfaceType(), ex);
+        }
+
+        // get matching service references
+        List<ServiceInfo> matchingServices = getMatchingServices(type, bundleContext);
+
+        // no references found? check if reference was optional
+        if (matchingServices.isEmpty()) {
+            boolean isOptional = (reference.getCardinality() == ReferenceCardinality.OPTIONAL_UNARY || reference
+                    .getCardinality() == ReferenceCardinality.OPTIONAL_MULTIPLE);
+            if (!isOptional) {
+                log.warn("Unable to inject mandatory reference '{}' for class {}", reference.getName(),
+                        targetClass.getName());
+            }
+            return isOptional;
+        }
+
+        // multiple references found? check if reference is not multiple
+        if (matchingServices.size() > 1
+                && (reference.getCardinality() == ReferenceCardinality.MANDATORY_UNARY || reference.getCardinality() == ReferenceCardinality.OPTIONAL_UNARY)) {
+            log.warn("Multiple matches found for unary reference '{}' for class {}", reference.getName(),
+                    targetClass.getName());
+            return false;
+        }
+
+        // try to invoke bind method
+        String bindMethodName = reference.getBind();
+        if (StringUtils.isNotEmpty(bindMethodName)) {
+            Method bindMethod = getFirstMethodWithNameAndSignature(targetClass, bindMethodName, new Class<?>[] { type });
+            if (bindMethod != null) {
+                bindMethod.setAccessible(true);
+                for (ServiceInfo matchingService : matchingServices) {
+                    try {
+                        bindMethod.invoke(target, matchingService.getServiceInstance());
+                    } catch (IllegalAccessException ex) {
+                        throw new RuntimeException("Unable to invoke method " + bindMethodName + " for class "
+                                + targetClass.getName(), ex);
+                    } catch (IllegalArgumentException ex) {
+                        throw new RuntimeException("Unable to invoke method " + bindMethodName + " for class "
+                                + targetClass.getName(), ex);
+                    } catch (InvocationTargetException ex) {
+                        throw new RuntimeException("Unable to invoke method " + bindMethodName + " for class "
+                                + targetClass.getName(), ex);
+                    }
+                }
+                return true;
+            } else {
+                Method bindMethodWithConfig = getFirstMethodWithNameAndSignature(targetClass, bindMethodName,
+                        new Class<?>[] { type, Map.class });
+                if (bindMethodWithConfig != null) {
+                    bindMethodWithConfig.setAccessible(true);
+                    for (ServiceInfo matchingService : matchingServices) {
+                        try {
+                            bindMethodWithConfig.invoke(target, matchingService.getServiceInstance(),
+                                    matchingService.getServiceConfig());
+                        } catch (IllegalAccessException ex) {
+                            throw new RuntimeException("Unable to invoke method " + bindMethodName + " for class "
+                                    + targetClass.getName(), ex);
+                        } catch (IllegalArgumentException ex) {
+                            throw new RuntimeException("Unable to invoke method " + bindMethodName + " for class "
+                                    + targetClass.getName(), ex);
+                        } catch (InvocationTargetException ex) {
+                            throw new RuntimeException("Unable to invoke method " + bindMethodName + " for class "
+                                    + targetClass.getName(), ex);
+                        }
+                    }
+                    return true;
+                } else {
+                    Method bindMethodServiceReference = getFirstMethodWithNameAndSignature(targetClass, bindMethodName,
+                            new Class<?>[] { ServiceReference.class });
+                    if (bindMethodServiceReference != null) {
+                        bindMethodServiceReference.setAccessible(true);
+                        for (ServiceInfo matchingService : matchingServices) {
+                            if (matchingService.getServiceReference() != null) {
+                                try {
+                                    bindMethodServiceReference.invoke(target, matchingService.getServiceReference());
+                                } catch (IllegalAccessException ex) {
+                                    throw new RuntimeException("Unable to invoke method " + bindMethodName
+                                            + " for class " + targetClass.getName(), ex);
+                                } catch (IllegalArgumentException ex) {
+                                    throw new RuntimeException("Unable to invoke method " + bindMethodName
+                                            + " for class " + targetClass.getName(), ex);
+                                } catch (InvocationTargetException ex) {
+                                    throw new RuntimeException("Unable to invoke method " + bindMethodName
+                                            + " for class " + targetClass.getName(), ex);
+                                }
+                            }
+                        }
+                        return true;
+                    }
+                }
+            }
+        }
+
+        log.warn("Bind method not found for reference '{}' for class {}", reference.getName(), targetClass.getName());
+        return false;
+    }
+
+    private static Method getFirstMethodWithNameAndSignature(Class<?> clazz, String methodName, Class<?>[] signature) {
+        Method[] methods = clazz.getDeclaredMethods();
+        for (Method method : methods) {
+            if (StringUtils.equals(method.getName(), methodName)
+                    && Arrays.equals(method.getParameterTypes(), signature)) {
+                return method;
+            }
+        }
+        // not found? check super classes
+        Class<?> superClass = clazz.getSuperclass();
+        if (superClass != null && superClass != Object.class) {
+            return getFirstMethodWithNameAndSignature(superClass, methodName, signature);
+        }
+        return null;
+    }
+
+    private static List<ServiceInfo> getMatchingServices(Class<?> type, BundleContext bundleContext) {
+        List<ServiceInfo> matchingServices = new ArrayList<ServiceInfo>();
+        try {
+            ServiceReference[] references = bundleContext.getServiceReferences(type.getName(), null);
+            if (references != null) {
+                for (ServiceReference serviceReference : references) {
+                    Object serviceInstance = bundleContext.getService(serviceReference);
+                    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+                    String[] keys = serviceReference.getPropertyKeys();
+                    for (String key : keys) {
+                        serviceConfig.put(key, serviceReference.getProperty(key));
+                    }
+                    matchingServices.add(new ServiceInfo(serviceInstance, serviceConfig, serviceReference));
+                }
+            }
+        } catch (InvalidSyntaxException ex) {
+            // ignore
+        }
+        return matchingServices;
+    }
+
+    private static class ServiceInfo {
+
+        private final Object serviceInstance;
+        private final Map<String, Object> serviceConfig;
+        private final ServiceReference serviceReference;
+
+        public ServiceInfo(Object serviceInstance, Map<String, Object> serviceConfig, ServiceReference serviceReference) {
+            this.serviceInstance = serviceInstance;
+            this.serviceConfig = serviceConfig;
+            this.serviceReference = serviceReference;
+        }
+
+        public Object getServiceInstance() {
+            return this.serviceInstance;
+        }
+
+        public Map<String, Object> getServiceConfig() {
+            return this.serviceConfig;
+        }
+
+        public ServiceReference getServiceReference() {
+            return serviceReference;
+        }
+
+    }
+
+}
diff --git a/src/main/resources/simplelogger.properties b/src/main/resources/simplelogger.properties
new file mode 100644
index 0000000..1507469
--- /dev/null
+++ b/src/main/resources/simplelogger.properties
@@ -0,0 +1,18 @@
+# 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.
+
+org.slf4j.simpleLogger.defaultLogLevel=warn
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
new file mode 100644
index 0000000..acf7d98
--- /dev/null
+++ b/src/site/markdown/index.md
@@ -0,0 +1,35 @@
+## About OSGi Mocks
+
+Mock implementation of selected OSGi APIs.
+
+### Maven Dependency
+
+```xml
+<dependency>
+  <groupId>org.apache.sling</groupId>
+  <artifactId>org.apache.sling.testing.osgi-mock</artifactId>
+  <version>1.0.0-SNAPHOT</version>
+</dependency>
+```
+
+### Documentation
+
+* [Usage](usage.html)
+* [API Documentation](apidocs/)
+* [Changelog](changes-report.html)
+
+### Implemented mock features
+
+The mock implementation supports:
+
+* Instantiating OSGi `Bundle`, `BundleContext` and `ComponentContext` objects and navigate between them.
+* Read and write properties on them.
+* Register OSGi services and get references to service instances
+* Service and bundle listener implementation
+* When adding services to BundleContext OSGi metadata from `/OSGI-INF/<pid>.xml` is read (e.g. for service ranking property)
+* Mock implementation of `LogService` which logs to SLF4J in JUnit context
+
+The following features are *not supported*:
+
+* Activation and deactivation methods of services are not called automatically (but helper methods exist)
+* Dependency injection does not take place automatically (but helper methods exist)
diff --git a/src/site/markdown/usage.md b/src/site/markdown/usage.md
new file mode 100644
index 0000000..c7e8bb8
--- /dev/null
+++ b/src/site/markdown/usage.md
@@ -0,0 +1,59 @@
+## Usage
+
+### Getting OSGi mock objects
+
+The factory class `MockOsgi` allows to instantiate the different mock implementations.
+
+Example:
+
+```java
+// get bundle context
+BundleContext bundleContext = MockOsgi.newBundleContext();
+
+// get component context
+Dictionary<String,Object> properties = new Hashtable<>();
+properties.put("prop1", "value1");
+BundleContext bundleContext = MockOsgi.newComponentContext(properties);
+```
+
+It is possible to simulate registering of OSGi services (backed by a simple hash map internally):
+
+```java
+// register service
+bundleContext.registerService(MyClass.class, myService, properties);
+
+// get service instance
+ServiceReference ref = bundleContext.getServiceReference(MyClass.class.getName());
+MyClass service = bundleContext.getService(ref);
+```
+
+### Activation and Dependency Injection
+
+It is possible to simulate OSGi service activation, deactivation and dependency injection and the mock implementation
+tries to to its best to execute all as expected for an OSGi environment.
+
+Example:
+
+```java
+// get bundle context
+BundleContext bundleContext = MockOsgi.newBundleContext();
+
+// create service instance manually
+MyService service = new MyService();
+
+// inject dependencies
+MockOsgi.injectServices(service, bundleContext);
+
+// activate service
+MockOsgi.activate(service, props);
+
+// operate with service...
+
+// deactivate service
+MockOsgi.deactivate(service);
+```
+
+Please note: The injectServices, activate and deactivate Methods can only work properly when the SCR XML metadata files
+are preset in the classpath at `/OSGI-INF`. They are generated automatically by the Maven SCR plugin, but might be
+missing if your clean and build the project within your IDE (e.g. Eclipse). In this case you have to compile the
+project again with maven and can run the tests - or use a Maven IDE Integration like m2eclipse.
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleContextTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleContextTest.java
new file mode 100644
index 0000000..6eef138
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleContextTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.osgi;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MockBundleContextTest {
+
+    private BundleContext bundleContext;
+
+    @Before
+    public void setUp() {
+        this.bundleContext = MockOsgi.newBundleContext();
+    }
+
+    @Test
+    public void testBundle() {
+        assertNotNull(this.bundleContext.getBundle());
+    }
+
+    @Test
+    public void testServiceRegistration() throws InvalidSyntaxException {
+        // prepare test services
+        String clazz1 = String.class.getName();
+        Object service1 = new Object();
+        Dictionary properties1 = getServiceProperties(null);
+        ServiceRegistration reg1 = this.bundleContext.registerService(clazz1, service1, properties1);
+
+        String[] clazzes2 = new String[] { String.class.getName(), Integer.class.getName() };
+        Object service2 = new Object();
+        Dictionary properties2 = getServiceProperties(null);
+        ServiceRegistration reg2 = this.bundleContext.registerService(clazzes2, service2, properties2);
+
+        String clazz3 = Integer.class.getName();
+        Object service3 = new Object();
+        Dictionary properties3 = getServiceProperties(100L);
+        ServiceRegistration reg3 = this.bundleContext.registerService(clazz3, service3, properties3);
+
+        // test get service references
+        ServiceReference refString = this.bundleContext.getServiceReference(String.class.getName());
+        assertSame(reg1.getReference(), refString);
+
+        ServiceReference refInteger = this.bundleContext.getServiceReference(Integer.class.getName());
+        assertSame(reg3.getReference(), refInteger);
+
+        ServiceReference[] refsString = this.bundleContext.getServiceReferences(String.class.getName(), null);
+        assertEquals(2, refsString.length);
+        assertSame(reg1.getReference(), refsString[0]);
+        assertSame(reg2.getReference(), refsString[1]);
+
+        ServiceReference[] refsInteger = this.bundleContext.getServiceReferences(Integer.class.getName(), null);
+        assertEquals(2, refsInteger.length);
+        assertSame(reg3.getReference(), refsInteger[0]);
+        assertSame(reg2.getReference(), refsInteger[1]);
+
+        ServiceReference[] allRefsString = this.bundleContext.getAllServiceReferences(String.class.getName(), null);
+        assertArrayEquals(refsString, allRefsString);
+
+        // test get services
+        assertSame(service1, this.bundleContext.getService(refsString[0]));
+        assertSame(service2, this.bundleContext.getService(refsString[1]));
+        assertSame(service3, this.bundleContext.getService(refInteger));
+
+        // unget does nothing
+        this.bundleContext.ungetService(refsString[0]);
+        this.bundleContext.ungetService(refsString[1]);
+        this.bundleContext.ungetService(refInteger);
+    }
+
+    private Dictionary getServiceProperties(final Long serviceRanking) {
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        if (serviceRanking != null) {
+            props.put(Constants.SERVICE_RANKING, serviceRanking);
+        }
+        return props;
+    }
+
+    @Test
+    public void testGetBundles() throws Exception {
+        assertEquals(0, this.bundleContext.getBundles().length);
+    }
+
+    @Test
+    public void testServiceListener() throws Exception {
+        ServiceListener serviceListener = mock(ServiceListener.class);
+        bundleContext.addServiceListener(serviceListener);
+
+        // prepare test services
+        String clazz1 = String.class.getName();
+        Object service1 = new Object();
+        this.bundleContext.registerService(clazz1, service1, null);
+
+        verify(serviceListener).serviceChanged(any(ServiceEvent.class));
+
+        bundleContext.removeServiceListener(serviceListener);
+    }
+
+    @Test
+    public void testBundleListener() throws Exception {
+        BundleListener bundleListener = mock(BundleListener.class);
+        BundleEvent bundleEvent = mock(BundleEvent.class);
+
+        bundleContext.addBundleListener(bundleListener);
+
+        MockOsgi.sendBundleEvent(bundleContext, bundleEvent);
+        verify(bundleListener).bundleChanged(bundleEvent);
+
+        bundleContext.removeBundleListener(bundleListener);
+    }
+
+    @Test
+    public void testFrameworkListener() throws Exception {
+        // ensure that listeners can be called (although they are not expected
+        // to to anything)
+        this.bundleContext.addFrameworkListener(null);
+        this.bundleContext.removeFrameworkListener(null);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleTest.java
new file mode 100644
index 0000000..7402241
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockBundleTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.osgi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+
+public class MockBundleTest {
+
+    private Bundle bundle;
+
+    @Before
+    public void setUp() {
+        this.bundle = MockOsgi.newBundleContext().getBundle();
+    }
+
+    @Test
+    public void testBundleId() {
+        assertTrue(this.bundle.getBundleId() > 0);
+    }
+
+    @Test
+    public void testBundleContxt() {
+        assertNotNull(this.bundle.getBundleContext());
+    }
+
+    @Test
+    public void testGetEntry() {
+        assertNotNull(this.bundle.getEntry("/META-INF/test.txt"));
+        assertNull(this.bundle.getEntry("/invalid"));
+    }
+
+    @Test
+    public void testGetStatie() {
+        assertEquals(Bundle.ACTIVE, bundle.getState());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockComponentContextTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockComponentContextTest.java
new file mode 100644
index 0000000..18f7ba4
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockComponentContextTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.osgi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+
+public class MockComponentContextTest {
+
+    private ComponentContext underTest;
+
+    @Before
+    public void setUp() {
+        underTest = MockOsgi.newComponentContext();
+    }
+
+    @Test
+    public void testBundleContext() {
+        assertNotNull(underTest.getBundleContext());
+    }
+
+    @Test
+    public void testInitialProperties() {
+        assertEquals(0, underTest.getProperties().size());
+    }
+
+    @Test
+    public void testProvidedProperties() {
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put("prop1", "value1");
+        props.put("prop2", 25);
+        ComponentContext componentContextWithProperties = MockOsgi.newComponentContext(props);
+
+        Dictionary contextProps = componentContextWithProperties.getProperties();
+        assertEquals(2, contextProps.size());
+        assertEquals("value1", contextProps.get("prop1"));
+        assertEquals(25, contextProps.get("prop2"));
+    }
+
+    @Test
+    public void testLocateService() {
+        // prepare test service
+        String clazz = String.class.getName();
+        Object service = new Object();
+        underTest.getBundleContext().registerService(clazz, service, null);
+        ServiceReference ref = underTest.getBundleContext().getServiceReference(clazz);
+
+        // test locate service
+        Object locatedService = underTest.locateService(null, ref);
+        assertSame(service, locatedService);
+    }
+
+    @Test
+    public void testIgnoredMethods() {
+        underTest.enableComponent("myComponent");
+        underTest.disableComponent("myComponent");
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockFilterTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockFilterTest.java
new file mode 100644
index 0000000..9c4a955
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockFilterTest.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.osgi;
+
+import static org.junit.Assert.assertFalse;
+
+import java.util.Hashtable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceReference;
+
+public class MockFilterTest {
+
+    private Filter filter;
+
+    @Before
+    public void setUp() {
+        this.filter = new MockFilter();
+    }
+
+    @Test
+    public void testDenyAll() {
+        assertFalse(this.filter.match((ServiceReference) null));
+        assertFalse(this.filter.match((Hashtable) null));
+        assertFalse(this.filter.matchCase(null));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockLogServiceTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockLogServiceTest.java
new file mode 100644
index 0000000..7fbd7ab
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockLogServiceTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.osgi;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.service.log.LogService;
+
+public class MockLogServiceTest {
+
+    private LogService logService;
+
+    @Before
+    public void setUp() throws Exception {
+        this.logService = new MockLogService(getClass());
+    }
+
+    @Test
+    public void testLog() {
+        this.logService.log(LogService.LOG_ERROR, "message 1");
+        this.logService.log(LogService.LOG_WARNING, "message 1");
+        this.logService.log(LogService.LOG_INFO, "message 1");
+        this.logService.log(LogService.LOG_DEBUG, "message 1");
+
+        this.logService.log(null, LogService.LOG_ERROR, "message 1");
+        this.logService.log(null, LogService.LOG_WARNING, "message 1");
+        this.logService.log(null, LogService.LOG_INFO, "message 1");
+        this.logService.log(null, LogService.LOG_DEBUG, "message 1");
+    }
+
+    @Test
+    public void testLogException() {
+        this.logService.log(LogService.LOG_ERROR, "message 2", new Exception());
+        this.logService.log(LogService.LOG_WARNING, "message 2", new Exception());
+        this.logService.log(LogService.LOG_INFO, "message 2", new Exception());
+        this.logService.log(LogService.LOG_DEBUG, "message 2", new Exception());
+
+        this.logService.log(null, LogService.LOG_ERROR, "message 2", new Exception());
+        this.logService.log(null, LogService.LOG_WARNING, "message 2", new Exception());
+        this.logService.log(null, LogService.LOG_INFO, "message 2", new Exception());
+        this.logService.log(null, LogService.LOG_DEBUG, "message 2", new Exception());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testLogInvalidLevel() {
+        this.logService.log(0, "message 1");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testLogExceptionInvalidLevel() {
+        this.logService.log(0, "message 2", new Exception());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/MockServiceReferenceTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/MockServiceReferenceTest.java
new file mode 100644
index 0000000..c525575
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/MockServiceReferenceTest.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.osgi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.sling.testing.mock.osgi.OsgiMetadataUtilTest.ServiceWithMetadata;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+public class MockServiceReferenceTest {
+
+    private BundleContext bundleContext;
+    private ServiceReference serviceReference;
+    private Object service;
+
+    @Before
+    public void setUp() {
+        this.bundleContext = MockOsgi.newBundleContext();
+
+        this.service = new Object();
+        String clazz = String.class.getName();
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put("customProp1", "value1");
+
+        this.bundleContext.registerService(clazz, this.service, props);
+        this.serviceReference = this.bundleContext.getServiceReference(clazz);
+    }
+
+    @Test
+    public void testBundle() {
+        assertSame(this.bundleContext.getBundle(), this.serviceReference.getBundle());
+    }
+
+    @Test
+    public void testServiceId() {
+        assertNotNull(this.serviceReference.getProperty(Constants.SERVICE_ID));
+    }
+
+    @Test
+    public void testProperties() {
+        assertEquals(2, this.serviceReference.getPropertyKeys().length);
+        assertEquals("value1", this.serviceReference.getProperty("customProp1"));
+    }
+
+    @Test
+    public void testWithOsgiMetadata() {
+        ServiceWithMetadata serviceWithMetadata = new OsgiMetadataUtilTest.ServiceWithMetadata();
+        bundleContext.registerService((String) null, serviceWithMetadata, null);
+        ServiceReference reference = this.bundleContext.getServiceReference(Comparable.class.getName());
+
+        assertEquals(5000, reference.getProperty("service.ranking"));
+        assertEquals("The Apache Software Foundation", reference.getProperty("service.vendor"));
+        assertEquals("org.apache.sling.models.impl.injectors.OSGiServiceInjector", reference.getProperty("service.pid"));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.java
new file mode 100644
index 0000000..52f9011
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/OsgiMetadataUtilTest.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.osgi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.Reference;
+import org.junit.Test;
+import org.w3c.dom.Document;
+
+public class OsgiMetadataUtilTest {
+
+    @Test
+    public void testMetadata() {
+        Document doc = OsgiMetadataUtil.getMetadata(ServiceWithMetadata.class);
+
+        Set<String> serviceInterfaces = OsgiMetadataUtil.getServiceInterfaces(ServiceWithMetadata.class, doc);
+        assertEquals(3, serviceInterfaces.size());
+        assertTrue(serviceInterfaces.contains("org.apache.sling.models.spi.Injector"));
+        assertTrue(serviceInterfaces
+                .contains("org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory"));
+        assertTrue(serviceInterfaces.contains("java.lang.Comparable"));
+
+        Map<String, Object> props = OsgiMetadataUtil.getProperties(ServiceWithMetadata.class, doc);
+        assertEquals(3, props.size());
+        assertEquals(5000, props.get("service.ranking"));
+        assertEquals("The Apache Software Foundation", props.get("service.vendor"));
+        assertEquals("org.apache.sling.models.impl.injectors.OSGiServiceInjector", props.get("service.pid"));
+    }
+
+    @Test
+    public void testNoMetadata() {
+        Document doc = OsgiMetadataUtil.getMetadata(ServiceWithoutMetadata.class);
+
+        Set<String> serviceInterfaces = OsgiMetadataUtil.getServiceInterfaces(ServiceWithoutMetadata.class, doc);
+        assertEquals(0, serviceInterfaces.size());
+
+        Map<String, Object> props = OsgiMetadataUtil.getProperties(ServiceWithoutMetadata.class, doc);
+        assertEquals(0, props.size());
+    }
+
+    @Test
+    public void testReferences() {
+        Document doc = OsgiMetadataUtil.getMetadata(ReflectionServiceUtilTest.Service3.class);
+        List<Reference> references = OsgiMetadataUtil.getReferences(ReflectionServiceUtilTest.Service3.class, doc);
+        assertEquals(3, references.size());
+
+        Reference ref1 = references.get(0);
+        assertEquals("reference2", ref1.getName());
+        assertEquals("org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface2", ref1.getInterfaceType());
+        assertEquals(ReferenceCardinality.MANDATORY_MULTIPLE, ref1.getCardinality());
+        assertEquals("bindReference2", ref1.getBind());
+        assertEquals("unbindReference2", ref1.getUnbind());
+    }
+
+    @Test
+    public void testActivateMethodName() {
+        Document doc = OsgiMetadataUtil.getMetadata(ReflectionServiceUtilTest.Service3.class);
+        String methodName = OsgiMetadataUtil.getActivateMethodName(ReflectionServiceUtilTest.Service3.class, doc);
+        assertEquals("activate", methodName);
+    }
+
+    static class ServiceWithMetadata {
+        // empty class
+    }
+
+    static class ServiceWithoutMetadata {
+        // empty class
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/ReflectionServiceUtilTest.java b/src/test/java/org/apache/sling/testing/mock/osgi/ReflectionServiceUtilTest.java
new file mode 100644
index 0000000..ff29037
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/ReflectionServiceUtilTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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.osgi;
+
+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.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.References;
+import org.apache.felix.scr.annotations.Service;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+
+public class ReflectionServiceUtilTest {
+
+    private BundleContext bundleContext = MockOsgi.newBundleContext();
+    private Service1 service1;
+    private Service2 service2;
+
+    @Before
+    public void setUp() {
+        service1 = new Service1();
+        service2 = new Service2();
+        bundleContext.registerService(ServiceInterface1.class.getName(), service1, null);
+        bundleContext.registerService(ServiceInterface2.class.getName(), service2, null);
+    }
+
+    @Test
+    public void testService3() {
+        Service3 service3 = new Service3();
+        assertTrue(MockOsgi.injectServices(service3, bundleContext));
+
+        Dictionary<String, Object> service3Config = new Hashtable<String, Object>();
+        service3Config.put("prop1", "value1");
+        assertTrue(MockOsgi.activate(service3, bundleContext, service3Config));
+
+        assertNotNull(service3.getComponentContext());
+        assertEquals(service3Config, service3.getComponentContext().getProperties());
+
+        assertSame(service1, service3.getReference1());
+
+        List<ServiceInterface2> references2 = service3.getReferences2();
+        assertEquals(1, references2.size());
+        assertSame(service2, references2.get(0));
+
+        List<ServiceInterface3> references3 = service3.getReferences3();
+        assertEquals(1, references3.size());
+        assertSame(service2, references3.get(0));
+
+        List<Map<String, Object>> reference3Configs = service3.getReference3Configs();
+        assertEquals(1, reference3Configs.size());
+        assertEquals(200, reference3Configs.get(0).get(Constants.SERVICE_RANKING));
+
+        assertTrue(MockOsgi.deactivate(service3));
+        assertNull(service3.getComponentContext());
+    }
+
+    @Test
+    public void testService4() {
+        Service4 service4 = new Service4();
+
+        assertTrue(MockOsgi.injectServices(service4, bundleContext));
+        assertFalse(MockOsgi.activate(service4));
+
+        assertSame(service1, service4.getReference1());
+    }
+
+    public interface ServiceInterface1 {
+        // no methods
+    }
+
+    public interface ServiceInterface2 {
+        // no methods
+    }
+
+    public interface ServiceInterface3 {
+        // no methods
+    }
+
+    @Component
+    @Service(ServiceInterface1.class)
+    @Property(name = Constants.SERVICE_RANKING, intValue = 100)
+    public static class Service1 implements ServiceInterface1 {
+        // dummy interface
+    }
+
+    @Component
+    @Service({ ServiceInterface2.class, ServiceInterface3.class })
+    @Property(name = Constants.SERVICE_RANKING, intValue = 200)
+    public static class Service2 implements ServiceInterface2, ServiceInterface3 {
+        // dummy interface
+    }
+
+    @Component
+    @References({ @Reference(name = "reference2", referenceInterface = ServiceInterface2.class, cardinality = ReferenceCardinality.MANDATORY_MULTIPLE) })
+    public static class Service3 {
+
+        @Reference
+        private ServiceInterface1 reference1;
+
+        private List<ServiceReference> references2 = new ArrayList<ServiceReference>();
+
+        @Reference(name = "reference3", referenceInterface = ServiceInterface3.class, cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE)
+        private List<ServiceInterface3> references3 = new ArrayList<ServiceInterface3>();
+        private List<Map<String, Object>> reference3Configs = new ArrayList<Map<String, Object>>();
+
+        private ComponentContext componentContext;
+
+        @Activate
+        private void activate(ComponentContext ctx) {
+            this.componentContext = ctx;
+        }
+
+        @Deactivate
+        private void deactivate(ComponentContext ctx) {
+            this.componentContext = null;
+        }
+
+        public ServiceInterface1 getReference1() {
+            return this.reference1;
+        }
+
+        public List<ServiceInterface2> getReferences2() {
+            List<ServiceInterface2> services = new ArrayList<ServiceInterface2>();
+            for (ServiceReference serviceReference : references2) {
+                services.add((ServiceInterface2)componentContext.getBundleContext().getService(serviceReference));
+            }
+            return services;
+        }
+
+        public List<ServiceInterface3> getReferences3() {
+            return this.references3;
+        }
+
+        public List<Map<String, Object>> getReference3Configs() {
+            return this.reference3Configs;
+        }
+
+        public ComponentContext getComponentContext() {
+            return this.componentContext;
+        }
+
+        protected void bindReference1(ServiceInterface1 service) {
+            reference1 = service;
+        }
+
+        protected void unbindReference1(ServiceInterface1 service) {
+            reference1 = null;
+        }
+
+        protected void bindReference2(ServiceReference serviceReference) {
+            references2.add(serviceReference);
+        }
+
+        protected void unbindReference2(ServiceReference serviceReference) {
+            references2.remove(serviceReference);
+        }
+
+        protected void bindReference3(ServiceInterface3 service, Map<String, Object> serviceConfig) {
+            references3.add(service);
+            reference3Configs.add(serviceConfig);
+        }
+
+        protected void unbindReference3(ServiceInterface3 service, Map<String, Object> serviceConfig) {
+            references3.remove(service);
+            reference3Configs.remove(serviceConfig);
+        }
+
+    }
+
+    @Component
+    @Reference(referenceInterface = ServiceInterface1.class, name = "customName", bind = "customBind", unbind = "customUnbind")
+    public static class Service4 {
+
+        private ServiceInterface1 reference1;
+
+        public ServiceInterface1 getReference1() {
+            return this.reference1;
+        }
+
+        protected void customBind(ServiceInterface1 service) {
+            reference1 = service;
+        }
+
+        protected void customUnbind(ServiceInterface1 service) {
+            reference1 = null;
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/testing/mock/osgi/package-info.java b/src/test/java/org/apache/sling/testing/mock/osgi/package-info.java
new file mode 100644
index 0000000..9c9ff28
--- /dev/null
+++ b/src/test/java/org/apache/sling/testing/mock/osgi/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 OSGi APIs.
+ */
+package org.apache.sling.testing.mock.osgi;
+
diff --git a/src/test/resources/META-INF/test.txt b/src/test/resources/META-INF/test.txt
new file mode 100644
index 0000000..af27ff4
--- /dev/null
+++ b/src/test/resources/META-INF/test.txt
@@ -0,0 +1 @@
+This is a test file.
\ No newline at end of file
diff --git a/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiMetadataUtilTest.xml b/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiMetadataUtilTest.xml
new file mode 100644
index 0000000..95ac503
--- /dev/null
+++ b/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.OsgiMetadataUtilTest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+  <scr:component name="org.apache.sling.testing.mock.osgi.OsgiMetadataUtilTest$ServiceWithMetadata" activate="activate">
+    <implementation class="org.apache.sling.testing.mock.osgi.OsgiMetadataUtilTest$ServiceWithMetadata"/>
+    <service servicefactory="false">
+      <provide interface="org.apache.sling.models.spi.Injector"/>
+      <provide interface="org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory"/>
+      <provide interface="java.lang.Comparable"/>
+    </service>
+    <property name="service.ranking" type="Integer" value="5000"/>
+    <property name="service.vendor" value="The Apache Software Foundation"/>
+    <property name="service.pid" value="org.apache.sling.models.impl.injectors.OSGiServiceInjector"/>
+  </scr:component>
+</components>
diff --git a/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest.xml b/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest.xml
new file mode 100644
index 0000000..923b90f
--- /dev/null
+++ b/src/test/resources/OSGI-INF/org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0">
+  <scr:component name="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service1">
+    <implementation class="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service1"/>
+    <service servicefactory="false">
+      <provide interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface1"/>
+    </service>
+    <property name="service.ranking" type="Integer" value="100"/>
+    <property name="service.pid" value="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service1"/>
+  </scr:component>
+  <scr:component name="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service2">
+    <implementation class="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service2"/>
+    <service servicefactory="false">
+      <provide interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface2"/>
+      <provide interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface3"/>
+    </service>
+    <property name="service.ranking" type="Integer" value="200"/>
+    <property name="service.pid" value="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service2"/>
+  </scr:component>
+  <scr:component name="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service3" activate="activate" deactivate="deactivate">
+    <implementation class="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service3"/>
+    <property name="service.pid" value="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service3"/>
+    <reference name="reference2" interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface2" cardinality="1..n" policy="static" bind="bindReference2" unbind="unbindReference2"/>
+    <reference name="reference1" interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface1" cardinality="1..1" policy="static" bind="bindReference1" unbind="unbindReference1"/>
+    <reference name="reference3" interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface3" cardinality="0..n" policy="static" bind="bindReference3" unbind="unbindReference3"/>
+  </scr:component>
+  <scr:component name="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service4">
+    <implementation class="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service4"/>
+    <property name="service.pid" value="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$Service4"/>
+    <reference name="customName" interface="org.apache.sling.testing.mock.osgi.ReflectionServiceUtilTest$ServiceInterface1" cardinality="1..1" policy="static" bind="customBind" unbind="customUnbind"/>
+  </scr:component>
+</components>
diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties
new file mode 100644
index 0000000..1299698
--- /dev/null
+++ b/src/test/resources/simplelogger.properties
@@ -0,0 +1,18 @@
+# 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.
+
+org.slf4j.simpleLogger.defaultLogLevel=error

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