diff --git a/examples/osgi-test-example-pde/README.md b/examples/osgi-test-example-pde/README.md
new file mode 100644
index 00000000..2222afa9
--- /dev/null
+++ b/examples/osgi-test-example-pde/README.md
@@ -0,0 +1,8 @@
+# osgi-test-example-pde
+An example project demonstrating OSGi testing capabilities using [Eclipse PDE](https://www.eclipse.org/pde/).
+
+To execute it do the following:
+
+- make sure you are using a recent Eclipse for RCP/RAP Developers edition (minimum 2022-03)
+- open the 'example.target' and set it as your active target
+- import the projects, right click on PlayerTest.java and choose `Run As` > `Junit Plugin Test`
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/.classpath b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/.classpath
new file mode 100644
index 00000000..e801ebfb
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/.classpath
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/.project b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/.project
new file mode 100644
index 00000000..b43f5861
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/.project
@@ -0,0 +1,28 @@
+
+
+ example.api
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/.settings/org.eclipse.jdt.core.prefs b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000..c9545f06
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
+org.eclipse.jdt.core.compiler.compliance=11
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=11
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/META-INF/MANIFEST.MF b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/META-INF/MANIFEST.MF
new file mode 100644
index 00000000..500357b0
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/META-INF/MANIFEST.MF
@@ -0,0 +1,8 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: API
+Bundle-SymbolicName: org.osgi.test.example.api
+Bundle-Version: 1.0.0.qualifier
+Automatic-Module-Name: org.osgi.test.example.api
+Bundle-RequiredExecutionEnvironment: JavaSE-11
+Export-Package: org.osgi.test.example.api;version="1.0.0"
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/build.properties b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/build.properties
new file mode 100644
index 00000000..34d2e4d2
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/build.properties
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/src/org/osgi/test/example/api/Ball.java b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/src/org/osgi/test/example/api/Ball.java
new file mode 100644
index 00000000..5238fadd
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/src/org/osgi/test/example/api/Ball.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package org.osgi.test.example.api;
+
+public interface Ball {
+
+ void inflate();
+
+ void kick();
+
+}
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/src/org/osgi/test/example/api/Player.java b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/src/org/osgi/test/example/api/Player.java
new file mode 100644
index 00000000..eb1f5559
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.api/src/org/osgi/test/example/api/Player.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package org.osgi.test.example.api;
+
+public interface Player {
+
+ Ball getBall();
+
+ void kickBall();
+}
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/.classpath b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/.classpath
new file mode 100644
index 00000000..37b3b397
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/.classpath
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/.project b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/.project
new file mode 100644
index 00000000..d1e77883
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/.project
@@ -0,0 +1,33 @@
+
+
+ example.player.impl
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+ org.eclipse.pde.ds.core.builder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/.settings/org.eclipse.jdt.core.prefs b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000..c9545f06
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
+org.eclipse.jdt.core.compiler.compliance=11
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=11
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/.settings/org.eclipse.pde.ds.annotations.prefs b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/.settings/org.eclipse.pde.ds.annotations.prefs
new file mode 100644
index 00000000..73a356b6
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/.settings/org.eclipse.pde.ds.annotations.prefs
@@ -0,0 +1,8 @@
+classpath=true
+dsVersion=V1_3
+eclipse.preferences.version=1
+enabled=true
+generateBundleActivationPolicyLazy=true
+path=OSGI-INF
+validationErrorLevel=error
+validationErrorLevel.missingImplicitUnbindMethod=error
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/META-INF/MANIFEST.MF b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/META-INF/MANIFEST.MF
new file mode 100644
index 00000000..1734013d
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/META-INF/MANIFEST.MF
@@ -0,0 +1,20 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Player Impl
+Bundle-SymbolicName: org.osgi.test.example.player.impl
+Bundle-Version: 1.0.0.qualifier
+Automatic-Module-Name: org.osgi.test.example.player.impl
+Bundle-RequiredExecutionEnvironment: JavaSE-11
+Import-Package: org.osgi.framework;version="1.10.0",
+ org.osgi.service.component.annotations;version="1.3.0",
+ org.osgi.test.example.api;version="[1.0.0,1.1.0)"
+Bundle-ActivationPolicy: lazy
+Require-Bundle: org.junit,
+ org.osgi.test.common;resolution:=optional,
+ assertj-core;resolution:=optional,
+ org.osgi.test.assertj.framework;resolution:=optional,
+ org.mockito.mockito-core;resolution:=optional,
+ org.osgi.test.junit5;bundle-version="1.1.0";resolution:=optional,
+ org.junit.jupiter.api;bundle-version="5.8.1";resolution:=optional
+Export-Package: org.osgi.test.example.player.test
+Service-Component: OSGI-INF/org.osgi.test.example.player.impl.PlayerImpl.xml
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/OSGI-INF/org.osgi.test.example.player.impl.PlayerImpl.xml b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/OSGI-INF/org.osgi.test.example.player.impl.PlayerImpl.xml
new file mode 100644
index 00000000..238aa0ca
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/OSGI-INF/org.osgi.test.example.player.impl.PlayerImpl.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/build.properties b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/build.properties
new file mode 100644
index 00000000..4974816f
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/org.osgi.test.example.player.impl.PlayerImpl.xml
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/src/org/osgi/test/example/player/impl/PlayerImpl.java b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/src/org/osgi/test/example/player/impl/PlayerImpl.java
new file mode 100644
index 00000000..e8bc6d07
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/src/org/osgi/test/example/player/impl/PlayerImpl.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package org.osgi.test.example.player.impl;
+
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.test.example.api.Ball;
+import org.osgi.test.example.api.Player;
+
+@Component
+public class PlayerImpl implements Player {
+
+ @Reference
+ Ball ball;
+
+ @Override
+ public void kickBall() {
+ ball.kick();
+ }
+
+ @Override
+ public Ball getBall() {
+ return ball;
+ }
+}
diff --git a/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/src_test/org/osgi/test/example/player/test/PlayerTest.java b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/src_test/org/osgi/test/example/player/test/PlayerTest.java
new file mode 100644
index 00000000..9a733f28
--- /dev/null
+++ b/examples/osgi-test-example-pde/bundles/org.osgi.test.example.player.impl/src_test/org/osgi/test/example/player/test/PlayerTest.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) Contributors to the Eclipse Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *******************************************************************************/
+
+package org.osgi.test.example.player.test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+import java.util.Dictionary;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.osgi.framework.BundleContext;
+import org.osgi.test.assertj.dictionary.DictionaryAssert;
+import org.osgi.test.common.annotation.InjectBundleContext;
+import org.osgi.test.common.annotation.InjectService;
+import org.osgi.test.common.dictionary.Dictionaries;
+import org.osgi.test.common.service.ServiceAware;
+import org.osgi.test.example.api.Ball;
+import org.osgi.test.example.api.Player;
+import org.osgi.test.junit5.context.BundleContextExtension;
+import org.osgi.test.junit5.service.ServiceExtension;
+
+@ExtendWith(BundleContextExtension.class)
+@ExtendWith(ServiceExtension.class)
+public class PlayerTest {
+
+ static Ball b;
+
+ @InjectBundleContext
+ BundleContext bc;
+
+ public PlayerTest() {
+ System.out.println();
+ }
+
+ @BeforeAll
+ static void beforeAll(@InjectBundleContext BundleContext staticBC) {
+ b = mock(Ball.class);
+ Dictionary props = Dictionaries.dictionaryOf("test", "testball");
+ staticBC.registerService(Ball.class, b, props);
+
+ }
+
+ @InjectService
+ Player p;
+
+ @Test
+ void myTest() {
+ assertThat(p).isNotNull();
+ assertThat(p.getBall()).isSameAs(b);
+ verifyNoInteractions(b);
+ p.kickBall();
+ verify(b).kick();
+ }
+
+ static class DummyBall implements Ball {
+
+ @Override
+ public void inflate() {
+ }
+
+ @Override
+ public void kick() {
+ }
+ }
+
+ @Test
+ void myServiceAwareTest(@InjectService(cardinality = 0) ServiceAware ball) {
+ assertThat(ball.getServices()).hasSize(1);
+ DictionaryAssert.assertThat(ball.getServiceReference().getProperties()).containsEntry("test", "testball");
+ bc.registerService(Ball.class, new DummyBall(), null);
+ assertThat(ball.getServices()).hasSize(2);
+ bc.registerService(Ball.class, new DummyBall(), null);
+ assertThat(ball.getServices()).hasSize(3);
+ }
+
+ @Nested
+ @TestInstance(Lifecycle.PER_CLASS)
+ class TwoBalls {
+ Ball b2;
+
+ @BeforeAll
+ void beforeAll(@InjectBundleContext BundleContext bc) {
+ b2 = mock(Ball.class);
+ bc.registerService(Ball.class, b2, null);
+ }
+
+ @Test
+ void twoBallTest(@InjectService(cardinality = 2) List services) {
+ assertThat(services).containsExactlyInAnyOrder(b, b2);
+ }
+ }
+
+ // This test should fail - proves that the second ball registered
+ // in the nested test gets cleaned up.
+// @Test
+// void myDoubleTest(@InjectService(cardinality = 2) List balls) {
+// }
+}
diff --git a/examples/osgi-test-example-pde/example.target b/examples/osgi-test-example-pde/example.target
new file mode 100644
index 00000000..d1de5ce9
--- /dev/null
+++ b/examples/osgi-test-example-pde/example.target
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+ org.mockito
+ mockito-core
+ 3.8.0
+ jar
+
+
+
+
+
+
+ org.osgi
+ org.osgi.test.assertj.framework
+ 1.1.0
+ jar
+
+
+ org.osgi
+ org.osgi.test.assertj.log
+ 1.1.0
+ jar
+
+
+ org.osgi
+ org.osgi.test.assertj.promise
+ 1.1.0
+ jar
+
+
+ org.osgi
+ org.osgi.test.common
+ 1.1.0
+ jar
+
+
+ org.osgi
+ org.osgi.test.junit5.cm
+ 1.1.0
+ jar
+
+
+ org.osgi
+ org.osgi.test.junit5.listeners.log.osgi
+ 1.1.0
+ jar
+
+
+ org.osgi
+ org.osgi.test.junit5
+ 1.1.0
+ jar
+
+
+
+
+
\ No newline at end of file