From 622b81d9e1db670ff0271e841f7642e47c84d0df Mon Sep 17 00:00:00 2001
From: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com>
Date: Fri, 3 May 2024 08:21:25 +0200
Subject: [PATCH] feat: add Mock-EDC and a sample how to use it (#1264)

* add mocked service layer (wip)

* move remaining services out

* add instr api

* instr api interface

* add docker file

* add exemplary test to show how mock-edc works

* documentation

* add docker publ

* add javadoc

* build image before test

* documentation [skip ci]

* source doc [skip ci]

* rename mock-edc -> mock-connector
---
 .github/workflows/publish-docker.yaml         |   3 +-
 .github/workflows/publish-new-release.yml     |   4 +-
 .github/workflows/verify.yaml                 |   4 +-
 docs/development/mock-edc.md                  | 220 ++++++++++++++++++
 .../runtime/mock-connector/build.gradle.kts   |  69 ++++++
 .../mock-connector/src/main/docker/Dockerfile |  51 ++++
 .../eclipse/tractusx/edc/mock/MatchType.java  |  32 +++
 .../edc/mock/MockServiceExtension.java        | 120 ++++++++++
 .../tractusx/edc/mock/RecordedRequest.java    | 100 ++++++++
 .../mock/RecordedResponseDeserializer.java    |  77 ++++++
 .../tractusx/edc/mock/ResponseQueue.java      | 144 ++++++++++++
 .../edc/mock/ServiceFailureDeserializer.java  |  55 +++++
 .../instrumentation/InstrumentationApi.java   |  65 ++++++
 .../InstrumentationApiController.java         |  70 ++++++
 .../mock/services/AbstractServiceStub.java    |  30 +++
 .../edc/mock/services/AssetServiceStub.java   |  64 +++++
 .../ContractAgreementServiceStub.java         |  54 +++++
 .../ContractDefinitionServiceStub.java        |  64 +++++
 .../ContractNegotiationServiceStub.java       |  72 ++++++
 .../services/PolicyDefinitionServiceStub.java |  65 ++++++
 .../services/TransferProcessServiceStub.java  | 100 ++++++++
 ...rg.eclipse.edc.spi.system.ServiceExtension |  21 ++
 .../edc/mock/RecordedRequestTest.java         |  73 ++++++
 .../src/test/resources/asset.creation.json    |  24 ++
 .../src/test/resources/asset.failure.json     |  24 ++
 gradle/libs.versions.toml                     |   1 +
 .../build.gradle.kts                          |  38 +++
 .../mockedc/UseMockConnectorSampleTest.java   | 143 ++++++++++++
 .../resources/asset.creation.failure.json     |  24 ++
 .../src/test/resources/asset.creation.json    |  23 ++
 .../src/test/resources/asset.request.json     |  22 ++
 .../test/resources/contractdef.creation.json  |  33 +++
 .../resources/transferprocess.request.json    |  21 ++
 settings.gradle.kts                           |   2 +
 34 files changed, 1908 insertions(+), 4 deletions(-)
 create mode 100644 docs/development/mock-edc.md
 create mode 100644 edc-tests/runtime/mock-connector/build.gradle.kts
 create mode 100644 edc-tests/runtime/mock-connector/src/main/docker/Dockerfile
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MatchType.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MockServiceExtension.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedRequest.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedResponseDeserializer.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ResponseQueue.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ServiceFailureDeserializer.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApi.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApiController.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AbstractServiceStub.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AssetServiceStub.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractAgreementServiceStub.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractDefinitionServiceStub.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractNegotiationServiceStub.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/PolicyDefinitionServiceStub.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/TransferProcessServiceStub.java
 create mode 100644 edc-tests/runtime/mock-connector/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
 create mode 100644 edc-tests/runtime/mock-connector/src/test/java/org/eclipse/tractusx/edc/mock/RecordedRequestTest.java
 create mode 100644 edc-tests/runtime/mock-connector/src/test/resources/asset.creation.json
 create mode 100644 edc-tests/runtime/mock-connector/src/test/resources/asset.failure.json
 create mode 100644 samples/testing-with-mocked-connector/build.gradle.kts
 create mode 100644 samples/testing-with-mocked-connector/src/test/java/org/eclipse/tractusx/edc/samples/mockedc/UseMockConnectorSampleTest.java
 create mode 100644 samples/testing-with-mocked-connector/src/test/resources/asset.creation.failure.json
 create mode 100644 samples/testing-with-mocked-connector/src/test/resources/asset.creation.json
 create mode 100644 samples/testing-with-mocked-connector/src/test/resources/asset.request.json
 create mode 100644 samples/testing-with-mocked-connector/src/test/resources/contractdef.creation.json
 create mode 100644 samples/testing-with-mocked-connector/src/test/resources/transferprocess.request.json

diff --git a/.github/workflows/publish-docker.yaml b/.github/workflows/publish-docker.yaml
index 89e98b1ea..bf5ac3b01 100644
--- a/.github/workflows/publish-docker.yaml
+++ b/.github/workflows/publish-docker.yaml
@@ -49,7 +49,8 @@ jobs:
                    { dir: edc-controlplane, img: edc-controlplane-postgresql-hashicorp-vault },
                    { dir: edc-controlplane, img: edc-controlplane-postgresql-azure-vault },
                    { dir: edc-dataplane,    img: edc-dataplane-azure-vault },
-                   { dir: edc-dataplane,    img: edc-dataplane-hashicorp-vault } ]
+                   { dir: edc-dataplane,    img: edc-dataplane-hashicorp-vault },
+                   { dir: edc-tests/runtime, img: mock-connector }]
     permissions:
       contents: write
       packages: write
diff --git a/.github/workflows/publish-new-release.yml b/.github/workflows/publish-new-release.yml
index 1c50db5ec..f87be958e 100644
--- a/.github/workflows/publish-new-release.yml
+++ b/.github/workflows/publish-new-release.yml
@@ -104,8 +104,8 @@ jobs:
                    { dir: edc-controlplane, img: edc-controlplane-postgresql-hashicorp-vault },
                    { dir: edc-controlplane, img: edc-controlplane-postgresql-azure-vault },
                    { dir: edc-dataplane,    img: edc-dataplane-azure-vault },
-                   { dir: edc-dataplane,    img: edc-dataplane-hashicorp-vault } ]
-
+                   { dir: edc-dataplane,    img: edc-dataplane-hashicorp-vault },
+                   { dir: edc-tests/runtime, img: mock-connector }]
     steps:
       - uses: actions/checkout@v4
       - name: Export RELEASE_VERSION env
diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml
index b4dd1bf4a..88bf8d850 100644
--- a/.github/workflows/verify.yaml
+++ b/.github/workflows/verify.yaml
@@ -95,7 +95,9 @@ jobs:
       - uses: ./.github/actions/setup-java
 
       - name: Run Integration tests
-        run: ./gradlew test -DincludeTags="ComponentTest"
+        run: |
+          ./gradlew :edc-tests:runtime:mock-connector:dockerize
+          ./gradlew test -DincludeTags="ComponentTest"
 
   api-tests:
     runs-on: ubuntu-latest
diff --git a/docs/development/mock-edc.md b/docs/development/mock-edc.md
new file mode 100644
index 000000000..2efdaf995
--- /dev/null
+++ b/docs/development/mock-edc.md
@@ -0,0 +1,220 @@
+# Using the Mock-Connector for contract-based testing
+
+Modern testing methodologies are based on small, independent units of code that have a defined behaviour.
+Implementations as well as testing should be fast, repeatable, continuous and easily maintainable. In the context of EDC
+that means, that downstream projects that are based on EDC should not need to run a fully-fledged connector runtime to
+test their workflows. While the Tractus-X EDC project did provide a pure in-memory runtime for testing, that still
+requires all the configuration and a complex runtime environment to work, which may be a high barrier of entry.
+
+For this reason, and to developers who primarily interact with the Management API of a connector, the Tractus-X EDC
+project provides a testing framework with an even smaller footprint called the "Mock-Connector". It is a Docker image, that
+contains just the Management API plus an instrumentation interface to enable developers to use this in their
+unit/component testing and in continuous integration.
+
+We call this "contract-based testing", as it defines the specified behaviour of an application (here: the connector).
+The Mock-Connector's Management API is guaranteed to behave exactly the same, in fact, it even runs the
+same code as a "real" EDC.
+
+## 1. The contract
+
+The [Management API spec](https://eclipse-edc.github.io/Connector/openapi/management-api/).
+
+### 1.1 Definition of terms
+
+- connector: runnable Java application that contains Tractus-X modules. Also referred to as: EDC, runtime, tx-edc
+- mock: a replacement for a collaborator object (class, component, application), where the behaviour can be
+  controlled
+- stub: very similar to a mock, but while a mock oftentimes is a drop-in _replacement_, a stub would be a
+  re-implementation with a fixed behaviour. Also referred to as: dummy
+- instrumentation: the process of setting up a mock to behave a certain way. Also referred to as priming the mock.
+
+## 2. Intended audience
+
+Developers who build their applications and systems based on EDC, and interact with EDC through the Management API can
+use the Mock-Connector to decrease friction by not having to spin up and configure a fully-fledged connector runtime.
+
+Developers who plan to work with (Tractus-X) EDC in another way, like directly using its Maven artifacts, or even by
+implementing a DSP protocol head are kindly redirected to
+the [additional references section](#5-references-and-further-reading).
+
+## 3. Use with TestContainers
+
+Mock-Connector should be used as Docker image, we publish it as `tractusx/edc-mock`.
+
+Using the Mock-Connector is very easy, we recommend usage via Testcontainers. For example, setting up a JUnit test for a
+client application using Testcontainers could be done as follows:
+
+```java
+
+@Testcontainers
+@ComponentTest
+public class UseMockedEdcSampleTest {
+    @Container
+    protected static GenericContainer<?> edcContainer = new GenericContainer<>("tractusx/edc-mock:latest")
+            .withEnv("WEB_HTTP_PORT", "8080")
+            .withEnv("WEB_HTTP_PATH", "/api")
+            .withEnv("WEB_HTTP_MANAGEMENT_PORT", "8081")
+            .withEnv("WEB_HTTP_MANAGEMENT_PATH", "/api/management")
+            .withExposedPorts(8080, 8081);
+    private int managementPort;
+    private int defaultPort;
+
+    @BeforeEach
+    void setup() {
+        managementPort = edcContainer.getMappedPort(8081);
+        defaultPort = edcContainer.getMappedPort(8080);
+    }
+}
+```
+
+This downloads and runs the Docker image for the Mock-Connector and supplies it with minimal configuration. Specifically, it
+exposes the Management API and the default context, because that is needed to set up the mock.
+
+> Please note that in
+> the [example](../../samples/testing-with-mocked-edc/src/test/java/org/eclipse/tractusx/edc/samples/mockedc/UseMockedEdcSampleTest.java),
+> the image name is `mock-edc` - that is because in our CI testing we build the image and then run the tests, so we
+> can't use the official image.
+
+### 3.1 Running a simple positive test
+
+Executing a simple request against the Management API of EDC can be done like this:
+
+```java
+
+@Test
+void test_getAsset() {
+    //prime the mock - post a RecordedRequest
+    setupNextResponse("asset.request.json");
+
+    // perform the actual Asset API request. In a real test scenario, this would be the client code we're testing, i.e. the
+    // System-under-Test (SuT).
+    var assetArray = mgmtRequest()
+            .contentType(ContentType.JSON)
+            .body("""
+                    {
+                      "@context": {
+                        "@vocab": "https://w3id.org/edc/v0.0.1/ns/"
+                      },
+                    "@type": "QuerySpec"
+                    }
+                    """)
+            .post("/v3/assets/request")
+            .then()
+            .log().ifError()
+            .statusCode(200)
+            .extract().body().as(JsonArray.class);
+
+    // assert the response
+    assertThat(assetArray).hasSize(1);
+    assertThat(assetArray.get(0).asJsonObject().get("properties"))
+            .hasFieldOrProperty("prop1")
+            .hasFieldOrProperty("id")
+            .hasFieldOrProperty("contenttype");
+}
+```
+
+### 3.2 Running a test expecting a failure
+
+```java
+
+@Test
+void test_apiNotAuthenticated_expect400() {
+    //prime the mock - post a RecordedRequest
+    setupNextResponse("asset.creation.failure.json");
+
+    // perform the actual Asset API request. In a real test scenario, this would be the client code we're testing, i.e. the
+    // System-under-Test (SuT).
+    var assetArray = mgmtRequest()
+            .contentType(ContentType.JSON)
+            .body("""
+                    {
+                      "@context": {
+                        "@vocab": "https://w3id.org/edc/v0.0.1/ns/"
+                      },
+                    "@type": "QuerySpec"
+                    }
+                    """)
+            .post("/v3/assets/request")
+            .then()
+            .log().ifError()
+            .statusCode(400)
+            .extract().body().as(JsonArray.class);
+
+    // assert the response contains error information
+    assertThat(assetArray).hasSize(1);
+    var errorObject = assetArray.get(0).asJsonObject();
+    assertThat(errorObject.get("message").toString()).contains("This user is not authorized, This is just a second error message");
+}
+```
+
+Note that the difference here is that we prime the mock with a different JSON file (more on that later), we expect a
+different HTTP response code, i.e. 400, and the response body contains an error object instead of an array of Assets.
+
+## 4. Request pipeline and the instrumentation API
+
+The Mock-Connector internally contains a pipeline of "recorded requests", much like mocked HTTP webservers, like Netty
+Mockserver or OkHttp MockWebServer. Out-of-the-box, that pipeline is empty, which means the Management API would always
+respond with an error like the following:
+
+```json
+[
+  {
+    "message": "Failure: no recorded request left in queue.",
+    "type": "InvalidRequest",
+    "path": null,
+    "invalidValue": null
+  }
+]
+```
+
+To get beyond that, we need to _prime_ the mock. That means, we need to tell it how to respond to the next request by
+inserting a "recorded request" into its request pipeline. In previous code examples, this was done using
+the `setupNextResponse()` method. Mock-Connector offers an instrumentation API which can be used to insert recorded requests,
+to clear the queue and to get a count.
+
+### 4.1 Recorded requests
+
+A `RecordedRequest` is a POJO, that tells the Mock-Connector how to respond to the _next_ Management API request. To that end,
+it contains the input parameter type, the data associated with it, plus the return value type plus - most importantly -
+the data that is supposed to be returned.
+
+Recall the [previous example](#31-running-a-simple-positive-test), which tests an Asset request. Thus, we have to prime
+the mock such that it responds with a list of `Asset` objects. The semantic being: "on the next request, respond
+with ...".
+
+The contents of the [asset.request.json](../../samples/testing-with-mocked-edc/src/test/resources/asset.request.json)
+contains a section that defines the `input`, which in this case is a `QuerySpec`, and the `output` is a list of `Asset`
+objects. The `data` section must then contain serialized JSON that matches the `class` property. For instance,
+the `data` section of the `input` must contain JSON that can be deserialized into an `Asset`.
+
+> _Note that the information about input and output datatypes must currently be obtained from the aggregate services.
+Here, that would be the `AssetService` interface. In future iterations there will be a more convenient way to obtain
+that information._
+
+> _Note that input argument type matching is currently not supported, it will come in future releases._
+
+### 4.2 Instrumentation API
+
+The instrumentation is done via a simple REST API:
+
+```shell
+GET /api/instrumentation/count  -> returns the number of requests in the queue
+GET /api/instrumentation        -> returns the list of queued requests 
+DELETE /api/instrumentation     -> clears the queue
+POST /api/instrumentation       -> adds a new RecordedRequest, JSON must be in the request body
+```
+
+## 5. References and further reading
+
+- A complete sample how to run a test using the Mock-Connector in a Testcontainer can be
+  found [here](../../samples/testing-with-mocked-edc)
+- To test compliance with DSP, use the [TCK](https://github.com/eclipse-dataspacetck/cvf)
+- A Mock-IATP runtime is planned for future releases.
+
+## 6. Future improvements
+
+- matching requests to endpoints to allow for a "from-now-on" semantic
+- introducing placeholders for domain objects to increase refactoring robustness
+- abstract description of the endpoint's inputs and outputs, so developers don't need to know about service signatures
+  anymore
+- request input matching
\ No newline at end of file
diff --git a/edc-tests/runtime/mock-connector/build.gradle.kts b/edc-tests/runtime/mock-connector/build.gradle.kts
new file mode 100644
index 000000000..267e440d2
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/build.gradle.kts
@@ -0,0 +1,69 @@
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+
+/********************************************************************************
+ * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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
+ ********************************************************************************/
+
+plugins {
+    `java-library`
+    id("application")
+    id("com.github.johnrengelman.shadow") version "8.1.1"
+    id("io.swagger.core.v3.swagger-gradle-plugin")
+}
+
+
+dependencies {
+    // compile-time dependencies
+    implementation(libs.edc.spi.boot)
+    implementation(libs.edc.spi.controlplane)
+    implementation(libs.edc.lib.util)
+
+    // runtime dependencies
+    runtimeOnly(libs.edc.core.connector)
+    runtimeOnly(libs.edc.boot)
+    runtimeOnly(libs.edc.api.management)
+    runtimeOnly(libs.edc.api.management.config)
+
+    runtimeOnly(libs.edc.ext.http)
+    runtimeOnly(libs.bundles.edc.monitoring)
+
+    // edc libs
+    runtimeOnly(libs.edc.ext.jsonld)
+
+    testImplementation(libs.edc.junit)
+    testImplementation(libs.assertj)
+}
+
+application {
+    mainClass.set("org.eclipse.edc.boot.system.runtime.BaseRuntime")
+}
+
+edcBuild {
+    publish.set(false)
+}
+
+tasks.withType<ShadowJar> {
+    exclude("**/pom.properties", "**/pom.xm")
+    mergeServiceFiles()
+    archiveFileName.set("${project.name}.jar")
+}
+
+
+application {
+    mainClass.set("org.eclipse.edc.boot.system.runtime.BaseRuntime")
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/docker/Dockerfile b/edc-tests/runtime/mock-connector/src/main/docker/Dockerfile
new file mode 100644
index 000000000..999103615
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/docker/Dockerfile
@@ -0,0 +1,51 @@
+#################################################################################
+#  Copyright (c) 2024 Bayerische Motoren Werk Aktiengesellschaft (BMW AG)
+#
+#  See the NOTICE file(s) distributed with this work for additional
+#  information regarding copyright ownership.
+#
+#  This program and the accompanying materials are made available under the
+#  terms of the Apache License, Version 2.0 which is available at
+#  https://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
+#################################################################################
+
+
+FROM eclipse-temurin:22_36-jre-alpine
+ARG JAR
+ARG OTEL_JAR
+ARG ADDITIONAL_FILES
+
+ARG APP_USER=docker
+ARG APP_UID=10100
+
+RUN addgroup --system "$APP_USER"
+
+RUN adduser \
+     --shell /sbin/nologin \
+     --disabled-password \
+     --gecos "" \
+     --ingroup "$APP_USER" \
+     --no-create-home \
+     --uid "$APP_UID" \
+     "$APP_USER"
+
+USER "$APP_USER"
+WORKDIR /app
+
+COPY ${JAR} edc-mock.jar
+COPY ${ADDITIONAL_FILES} ./
+
+HEALTHCHECK NONE
+
+CMD ["java", \
+     "-Djava.security.egd=file:/dev/urandom", \
+     "-jar", \
+     "edc-mock.jar"]
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MatchType.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MatchType.java
new file mode 100644
index 000000000..fcdcacbce
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MatchType.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock;
+
+/**
+ * Represents how arguments are matched.
+ * <ul>
+ *     <li>ClASS: only the type must match, similar to Mockito's {@code isA(SomeType.class}</li>
+ *     <li>PARTIAL: only the specified properties must match, disregarding others</li>
+ *     <li>PARTIAL: all properties must match, those that are not listed are expected to be null</li>
+ * </ul>
+ */
+public enum MatchType {
+    CLASS, PARTIAL, EXACT
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MockServiceExtension.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MockServiceExtension.java
new file mode 100644
index 000000000..7176f87dd
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MockServiceExtension.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock;
+
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.eclipse.edc.connector.controlplane.services.spi.asset.AssetService;
+import org.eclipse.edc.connector.controlplane.services.spi.catalog.CatalogService;
+import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService;
+import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService;
+import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService;
+import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService;
+import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService;
+import org.eclipse.edc.runtime.metamodel.annotation.Inject;
+import org.eclipse.edc.runtime.metamodel.annotation.Provider;
+import org.eclipse.edc.spi.monitor.Monitor;
+import org.eclipse.edc.spi.query.QuerySpec;
+import org.eclipse.edc.spi.response.StatusResult;
+import org.eclipse.edc.spi.result.ServiceFailure;
+import org.eclipse.edc.spi.system.ServiceExtension;
+import org.eclipse.edc.spi.system.ServiceExtensionContext;
+import org.eclipse.edc.spi.types.TypeManager;
+import org.eclipse.edc.web.spi.WebService;
+import org.eclipse.tractusx.edc.mock.api.instrumentation.InstrumentationApiController;
+import org.eclipse.tractusx.edc.mock.services.AssetServiceStub;
+import org.eclipse.tractusx.edc.mock.services.ContractAgreementServiceStub;
+import org.eclipse.tractusx.edc.mock.services.ContractDefinitionServiceStub;
+import org.eclipse.tractusx.edc.mock.services.ContractNegotiationServiceStub;
+import org.eclipse.tractusx.edc.mock.services.PolicyDefinitionServiceStub;
+import org.eclipse.tractusx.edc.mock.services.TransferProcessServiceStub;
+
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class MockServiceExtension implements ServiceExtension {
+    private final Queue<RecordedRequest<?, ?>> recordedRequests = new ConcurrentLinkedQueue<>();
+    @Inject
+    private TypeManager typeManager;
+
+    @Inject
+    private WebService webService;
+
+    private Monitor monitor;
+
+    @Override
+    public void initialize(ServiceExtensionContext context) {
+        monitor = context.getMonitor().withPrefix("ResponseQueue");
+        webService.registerResource(new InstrumentationApiController(new ResponseQueue(recordedRequests, monitor)));
+
+        // register custom deserializer for the ServiceFailure
+        var mapper = typeManager.getMapper();
+        var module = new SimpleModule();
+        module.addDeserializer(ServiceFailure.class, new ServiceFailureDeserializer());
+        mapper.registerModule(module);
+    }
+
+    @Provider
+    public AssetService mockAssetService(ServiceExtensionContext context) {
+        var monitor = context.getMonitor().withPrefix("ResponseQueue");
+        return new AssetServiceStub(new ResponseQueue(recordedRequests, monitor));
+    }
+
+    @Provider
+    public CatalogService mockCatalogService() {
+        return new CatalogService() {
+            @Override
+            public CompletableFuture<StatusResult<byte[]>> requestCatalog(String counterPartyId, String counterPartyAddress, String protocol, QuerySpec querySpec) {
+                return null;
+            }
+
+            @Override
+            public CompletableFuture<StatusResult<byte[]>> requestDataset(String id, String counterPartyId, String counterPartyAddress, String protocol) {
+                return null;
+            }
+        };
+    }
+
+    @Provider
+    public ContractAgreementService mockContractAgreementService() {
+        return new ContractAgreementServiceStub(new ResponseQueue(recordedRequests, monitor));
+    }
+
+    @Provider
+    public ContractDefinitionService mockContractDefService() {
+        return new ContractDefinitionServiceStub(new ResponseQueue(recordedRequests, monitor));
+    }
+
+    @Provider
+    public ContractNegotiationService mockContractNegService() {
+        return new ContractNegotiationServiceStub(new ResponseQueue(recordedRequests, monitor));
+    }
+
+    @Provider
+    public PolicyDefinitionService mockPolicyDefService() {
+        return new PolicyDefinitionServiceStub(new ResponseQueue(recordedRequests, monitor));
+    }
+
+    @Provider
+    public TransferProcessService mockTransferProcessService() {
+        return new TransferProcessServiceStub(new ResponseQueue(recordedRequests, monitor));
+    }
+
+}
\ No newline at end of file
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedRequest.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedRequest.java
new file mode 100644
index 000000000..6b47b81be
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedRequest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.eclipse.edc.spi.result.ServiceFailure;
+
+/**
+ * Represents a request, that the service stub will replay. It has an input object (can be null), an output object, some metadata
+ * like name and description and a {@link MatchType}.
+ * In addition, it can have a {@link ServiceFailure}, in which case the {@code output} object is disregarded and the failure is always returned.
+ * This can be used to mock a failed API call.
+ */
+@JsonDeserialize(using = RecordedResponseDeserializer.class)
+public final class RecordedRequest<I, O> {
+    private final I input;
+    private final O output;
+    private String description;
+    private String name;
+    private MatchType inputMatchType;
+    private ServiceFailure failure;
+
+    private RecordedRequest(I input, O output) {
+        this.input = input;
+        this.output = output;
+    }
+
+    public I getInput() {
+        return input;
+    }
+
+    public O getOutput() {
+        return output;
+    }
+
+    public MatchType getInputMatchType() {
+        return inputMatchType;
+    }
+
+    public ServiceFailure getFailure() {
+        return failure;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public static class Builder<I, O> {
+        private final RecordedRequest<?, ?> instance;
+
+        public Builder(I input, O output) {
+            instance = new RecordedRequest<>(input, output);
+        }
+
+        public Builder<I, O> inputMatchType(MatchType input) {
+            instance.inputMatchType = input;
+            return this;
+        }
+
+        public Builder<I, O> name(String name) {
+            instance.name = name;
+            return this;
+        }
+
+        public Builder<I, O> description(String description) {
+            instance.description = description;
+            return this;
+        }
+
+        public Builder<I, O> failure(ServiceFailure failure) {
+            instance.failure = failure;
+            return this;
+        }
+
+        public RecordedRequest<?, ?> build() {
+            return instance;
+        }
+    }
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedResponseDeserializer.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedResponseDeserializer.java
new file mode 100644
index 000000000..d532ae1db
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedResponseDeserializer.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.eclipse.edc.spi.result.ServiceFailure;
+
+import java.io.IOException;
+import java.util.Optional;
+
+/**
+ * Custom deserializer for a {@link RecordedRequest} object, to be able to instantiate the input and output objects according
+ * to their class description.
+ */
+class RecordedResponseDeserializer extends StdDeserializer<RecordedRequest<?, ?>> {
+
+    public static final String INPUT_OBJECT = "input";
+    public static final String OUTPUT_OBJECT = "output";
+    public static final String CLASS_FIELD = "class";
+    public static final String DATA_FIELD = "data";
+    public static final String INPUT_MATCH_TYPE_FIELD = "match_type";
+    public static final String NAME_FIELD = "name";
+    public static final String DESCRIPTION_FIELD = "description";
+    private static final String FAILURE_OBJECT = "failure";
+
+    RecordedResponseDeserializer() {
+        this(null);
+    }
+
+    protected RecordedResponseDeserializer(Class<?> vc) {
+        super(vc);
+    }
+
+    @Override
+    public RecordedRequest<?, ?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
+        JsonNode node = jp.getCodec().readTree(jp);
+        var input = node.get(INPUT_OBJECT);
+        var output = node.get(OUTPUT_OBJECT);
+        try {
+            var inputClass = Class.forName(input.get(CLASS_FIELD).asText());
+            var outputClass = Class.forName(output.get(CLASS_FIELD).asText());
+
+            var inputObj = ctxt.readTreeAsValue(input.get(DATA_FIELD), inputClass);
+            var matchType = Optional.ofNullable(input.get("matchType")).map(JsonNode::asText).map(MatchType::valueOf).orElse(MatchType.CLASS);
+            var outputObj = ctxt.readTreeAsValue(output.get(DATA_FIELD), outputClass);
+
+            return new RecordedRequest.Builder(inputObj, outputObj)
+                    .inputMatchType(matchType)
+                    .failure(ctxt.readTreeAsValue(node.get(FAILURE_OBJECT), ServiceFailure.class))
+                    .name(Optional.ofNullable(node.get(NAME_FIELD)).map(JsonNode::asText).orElse(null))
+                    .description(Optional.ofNullable(node.get(DESCRIPTION_FIELD)).map(JsonNode::asText).orElse(null))
+                    .build();
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ResponseQueue.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ResponseQueue.java
new file mode 100644
index 000000000..d4839b1d1
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ResponseQueue.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock;
+
+import org.eclipse.edc.spi.EdcException;
+import org.eclipse.edc.spi.monitor.Monitor;
+import org.eclipse.edc.spi.result.Result;
+import org.eclipse.edc.spi.result.ServiceFailure;
+import org.eclipse.edc.spi.result.ServiceResult;
+import org.eclipse.edc.web.spi.exception.InvalidRequestException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * Container object that maintains the queue of {@link RecordedRequest} objects and grants high-level access to it.
+ */
+public class ResponseQueue {
+    private final Queue<RecordedRequest<?, ?>> recordedRequests; // todo guard access with locks?
+    private final Monitor monitor;
+
+    public ResponseQueue(Queue<RecordedRequest<?, ?>> recordedRequests, Monitor monitor) {
+        this.recordedRequests = recordedRequests;
+        this.monitor = monitor;
+    }
+
+    public <T> ServiceResult<T> getNext(Class<T> outputClass, String errorMessageTemplate) {
+        try {
+            return getNext(outputClass);
+        } catch (ClassCastException ex) {
+            var message = errorMessageTemplate.formatted(ex.getMessage());
+            monitor.severe(message); // no need for the entire stack trace
+            return ServiceResult.badRequest(message);
+        }
+    }
+
+    /**
+     * Gets the next item from the request queue and wraps it in a {@link ServiceResult}, where the generic type is a list type.
+     * To do that, the class of list type is expected as parameter.
+     *
+     * @param arrayElementClass    The type of elements that are expected to be in the list
+     * @param errorMessageTemplate An error string template that should contain the '%s' placeholder to receive additional information
+     * @return A {@link ServiceResult} that contains the payload of the next request
+     */
+    @SuppressWarnings("unchecked")
+    public <T> ServiceResult<List<T>> getNextAsList(Class<T> arrayElementClass, String errorMessageTemplate) {
+        if (arrayElementClass.isArray()) {
+            return ServiceResult.badRequest("First parameter must be type of list elements. For example, pass Object.class if a List<Object> is expected, but '%s' was passed".formatted(arrayElementClass.getName()));
+        }
+        var r = Result.ofThrowable(() -> {
+            T[] serviceResult = (T[]) getNext(arrayElementClass.arrayType(), errorMessageTemplate).orElseThrow(f -> new InvalidRequestException(f.getFailureDetail()));
+            return Arrays.asList(serviceResult);
+        });
+        if (r.succeeded()) {
+            return ServiceResult.success(r.getContent());
+        }
+        monitor.severe(errorMessageTemplate.formatted(r.getFailureDetail()));
+        return ServiceResult.badRequest(r.getFailureDetail());
+    }
+
+    /**
+     * clear the queue
+     */
+    public void clear() {
+        recordedRequests.clear();
+    }
+
+    /**
+     * adds a {@link RecordedRequest}
+     */
+    public void append(RecordedRequest<?, ?> recordedRequest) {
+        recordedRequests.offer(recordedRequest);
+    }
+
+    /**
+     * provides the contents of the queue as a list
+     */
+    public List<RecordedRequest<?, ?>> toList() {
+        return recordedRequests.stream().toList(); //immutable
+    }
+
+    /**
+     * Takes the next element from the queue and converts it into a {@link ServiceResult}
+     *
+     * @param outputType The desired output type. If the type description in the {@link RecordedRequest} does not match, a failure is returned.
+     */
+    @SuppressWarnings("unchecked")
+    private <T> ServiceResult<T> getNext(Class<T> outputType) {
+        monitor.debug("Get next recorded request, expect output of type %s".formatted(outputType));
+        var r = recordedRequests.poll();
+
+
+        if (r != null) {
+
+            if (r.getFailure() != null) {
+                return createResult(r.getFailure());
+            }
+
+            monitor.debug("Recorded request fetched, %d remaining.".formatted(recordedRequests.size()));
+            var recipeOutputType = r.getOutput().getClass();
+            if (!recipeOutputType.isAssignableFrom(outputType)) {
+                return ServiceResult.badRequest("Type mismatch: service invocation requires output type '%s', but Recipe specifies '%s'".formatted(outputType, recipeOutputType));
+            }
+            var output = (T) r.getOutput();
+            return ServiceResult.success(output);
+        }
+        var message = "Failure: no recorded request left in queue.";
+        monitor.debug(message);
+        return ServiceResult.badRequest(message);
+    }
+
+    // hack to access a protected constructor of the ServiceFailure
+    private <T> ServiceResult<T> createResult(ServiceFailure failure) {
+        try {
+            var ctor = ServiceResult.class.getDeclaredConstructor(Object.class, ServiceFailure.class);
+            ctor.setAccessible(true); // hack hack hack! I feel dirty doing this...
+            return (ServiceResult<T>) ctor.newInstance(null, failure);
+        } catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
+                 InvocationTargetException e) {
+            throw new EdcException(e);
+        }
+    }
+
+    //todo: add method getNextWithMatch that accepts an input and a match type
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ServiceFailureDeserializer.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ServiceFailureDeserializer.java
new file mode 100644
index 000000000..4fa07eb41
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ServiceFailureDeserializer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.eclipse.edc.spi.result.ServiceFailure;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Custom deserializer for {@link ServiceFailure}. We need this because there is no default
+ * CTor, and the public constructor with args is not annotated with {@link com.fasterxml.jackson.annotation.JsonProperty}.
+ */
+public class ServiceFailureDeserializer extends StdDeserializer<ServiceFailure> {
+    public static final String REASON_FIELD = "reason";
+    public static final String MESSAGES_FIELD = "messages";
+
+    protected ServiceFailureDeserializer(Class<?> vc) {
+        super(vc);
+    }
+
+    public ServiceFailureDeserializer() {
+        this(null);
+    }
+
+    @Override
+    public ServiceFailure deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
+        JsonNode node = jp.getCodec().readTree(jp);
+        var reason = ServiceFailure.Reason.valueOf(node.get(REASON_FIELD).asText());
+        var msgs = Arrays.asList(ctxt.readTreeAsValue(node.get(MESSAGES_FIELD), String[].class));
+
+        return new ServiceFailure(msgs, reason);
+    }
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApi.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApi.java
new file mode 100644
index 000000000..192069756
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApi.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock.api.instrumentation;
+
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.info.Info;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.eclipse.edc.web.spi.ApiErrorDetail;
+import org.eclipse.tractusx.edc.mock.RecordedRequest;
+
+import java.util.List;
+
+@OpenAPIDefinition(info = @Info(description = "This API allows to insert ", title = "Business Partner Group API"))
+@Tag(name = "Business Partner Group")
+public interface InstrumentationApi {
+
+    @Operation(description = "Adds a new RecordedRequest to the end of the queue.",
+            responses = {
+                    @ApiResponse(responseCode = "204", description = "The negotiation was successfully initiated."),
+                    @ApiResponse(responseCode = "400", description = "Request body was malformed",
+                            content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))),
+            })
+    void addNewRequest(RecordedRequest<?, ?> recordedRequest);
+
+    @Operation(description = "Clears the entire request queue.",
+            responses = {
+                    @ApiResponse(responseCode = "204", description = "The queue was successfully cleared.")
+            })
+    void clearQueue();
+
+    @Operation(description = "Return the entire request queue.",
+            responses = {
+                    @ApiResponse(responseCode = "200", description = "The list of RecordedRequest objects.",
+                            content = @Content(array = @ArraySchema(schema = @Schema(implementation = RecordedRequest.class)))),
+            })
+    List<RecordedRequest<?, ?>> getRequests();
+
+    @Operation(description = "Return amount of items currently in the queue.",
+            responses = {
+                    @ApiResponse(responseCode = "200")
+            })
+    int count();
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApiController.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApiController.java
new file mode 100644
index 000000000..ab2d100b7
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApiController.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock.api.instrumentation;
+
+
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import org.eclipse.tractusx.edc.mock.RecordedRequest;
+import org.eclipse.tractusx.edc.mock.ResponseQueue;
+
+import java.util.List;
+
+@Consumes({ MediaType.APPLICATION_JSON })
+@Produces({ MediaType.APPLICATION_JSON })
+@Path("/instrumentation")
+public class InstrumentationApiController implements InstrumentationApi {
+
+    private final ResponseQueue responseQueue;
+
+    public InstrumentationApiController(ResponseQueue responseQueue) {
+        this.responseQueue = responseQueue;
+    }
+
+    @Override
+    @POST
+    public void addNewRequest(RecordedRequest<?, ?> recordedRequest) {
+        responseQueue.append(recordedRequest);
+    }
+
+    @Override
+    @DELETE
+    public void clearQueue() {
+        responseQueue.clear();
+    }
+
+    @Override
+    @GET
+    public List<RecordedRequest<?, ?>> getRequests() {
+        return responseQueue.toList();
+    }
+
+    @Override
+    @GET
+    @Path("/count")
+    public int count() {
+        return responseQueue.toList().size();
+    }
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AbstractServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AbstractServiceStub.java
new file mode 100644
index 000000000..56fae0e66
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AbstractServiceStub.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock.services;
+
+import org.eclipse.tractusx.edc.mock.ResponseQueue;
+
+public abstract class AbstractServiceStub {
+    protected final ResponseQueue responseQueue;
+
+    public AbstractServiceStub(ResponseQueue responseQueue) {
+        this.responseQueue = responseQueue;
+    }
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AssetServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AssetServiceStub.java
new file mode 100644
index 000000000..1b58cdd94
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AssetServiceStub.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock.services;
+
+import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset;
+import org.eclipse.edc.connector.controlplane.services.spi.asset.AssetService;
+import org.eclipse.edc.spi.query.QuerySpec;
+import org.eclipse.edc.spi.result.ServiceResult;
+import org.eclipse.edc.web.spi.exception.InvalidRequestException;
+import org.eclipse.tractusx.edc.mock.ResponseQueue;
+
+import java.util.List;
+
+public class AssetServiceStub implements AssetService {
+
+    private final ResponseQueue responseQueue;
+
+    public AssetServiceStub(ResponseQueue responseQueue) {
+        this.responseQueue = responseQueue;
+    }
+
+    @Override
+    public Asset findById(String assetId) {
+        return responseQueue.getNext(Asset.class, "Error finding asset by ID: %s")
+                .orElseThrow(InvalidRequestException::new);
+    }
+
+    @Override
+    public ServiceResult<List<Asset>> search(QuerySpec query) {
+        return responseQueue.getNextAsList(Asset.class, "Error executing asset search: %s");
+    }
+
+    @Override
+    public ServiceResult<Asset> create(Asset asset) {
+        return responseQueue.getNext(Asset.class, "Error executing asset creation: %s");
+    }
+
+    @Override
+    public ServiceResult<Asset> delete(String assetId) {
+        return responseQueue.getNext(Asset.class, "Error executing asset deletion: %s");
+    }
+
+    @Override
+    public ServiceResult<Asset> update(Asset asset) {
+        return responseQueue.getNext(Asset.class, "Error executing asset update: %s");
+    }
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractAgreementServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractAgreementServiceStub.java
new file mode 100644
index 000000000..637e213a6
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractAgreementServiceStub.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock.services;
+
+import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement;
+import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation;
+import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService;
+import org.eclipse.edc.spi.query.QuerySpec;
+import org.eclipse.edc.spi.result.ServiceResult;
+import org.eclipse.edc.web.spi.exception.InvalidRequestException;
+import org.eclipse.tractusx.edc.mock.ResponseQueue;
+
+import java.util.List;
+
+public class ContractAgreementServiceStub extends AbstractServiceStub implements ContractAgreementService {
+
+    public ContractAgreementServiceStub(ResponseQueue responseQueue) {
+        super(responseQueue);
+    }
+
+    @Override
+    public ContractAgreement findById(String contractAgreementId) {
+        return responseQueue.getNext(ContractAgreement.class, "Error finding ContractAgreement: %s")
+                .orElseThrow(InvalidRequestException::new);
+    }
+
+    @Override
+    public ServiceResult<List<ContractAgreement>> search(QuerySpec query) {
+        return responseQueue.getNextAsList(ContractAgreement.class, "Error searching ContractAgreement: %s");
+    }
+
+    @Override
+    public ContractNegotiation findNegotiation(String contractAgreementId) {
+        return responseQueue.getNext(ContractNegotiation.class, "Error finding ContractNegotiation: %s")
+                .orElseThrow(InvalidRequestException::new);
+    }
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractDefinitionServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractDefinitionServiceStub.java
new file mode 100644
index 000000000..9435ff6f1
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractDefinitionServiceStub.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock.services;
+
+import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition;
+import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService;
+import org.eclipse.edc.spi.query.QuerySpec;
+import org.eclipse.edc.spi.result.ServiceResult;
+import org.eclipse.edc.web.spi.exception.InvalidRequestException;
+import org.eclipse.tractusx.edc.mock.ResponseQueue;
+
+import java.util.List;
+
+public class ContractDefinitionServiceStub extends AbstractServiceStub implements ContractDefinitionService {
+
+
+    public ContractDefinitionServiceStub(ResponseQueue responseQueue) {
+        super(responseQueue);
+
+    }
+
+    @Override
+    public ContractDefinition findById(String contractDefinitionId) {
+        return responseQueue.getNext(ContractDefinition.class, "Error finding ContractDefinition: %s")
+                .orElseThrow(InvalidRequestException::new);
+    }
+
+    @Override
+    public ServiceResult<List<ContractDefinition>> search(QuerySpec query) {
+        return responseQueue.getNextAsList(ContractDefinition.class, "Error searching ContractDefinition: %s");
+    }
+
+    @Override
+    public ServiceResult<ContractDefinition> create(ContractDefinition contractDefinition) {
+        return responseQueue.getNext(ContractDefinition.class, "Error creating ContractDefinition: %s");
+    }
+
+    @Override
+    public ServiceResult<Void> update(ContractDefinition contractDefinition) {
+        return responseQueue.getNext(Void.class, "Error updating ContractDefinition: %s");
+    }
+
+    @Override
+    public ServiceResult<ContractDefinition> delete(String contractDefinitionId) {
+        return responseQueue.getNext(ContractDefinition.class, "Error deleting ContractDefinition: %s");
+    }
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractNegotiationServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractNegotiationServiceStub.java
new file mode 100644
index 000000000..ada18ab41
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractNegotiationServiceStub.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock.services;
+
+import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement;
+import org.eclipse.edc.connector.controlplane.contract.spi.types.command.TerminateNegotiationCommand;
+import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation;
+import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractRequest;
+import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService;
+import org.eclipse.edc.spi.query.QuerySpec;
+import org.eclipse.edc.spi.result.ServiceResult;
+import org.eclipse.edc.web.spi.exception.InvalidRequestException;
+import org.eclipse.tractusx.edc.mock.ResponseQueue;
+
+import java.util.List;
+
+public class ContractNegotiationServiceStub extends AbstractServiceStub implements ContractNegotiationService {
+    public ContractNegotiationServiceStub(ResponseQueue responseQueue) {
+        super(responseQueue);
+    }
+
+    @Override
+    public ContractNegotiation findbyId(String contractNegotiationId) {
+        return responseQueue.getNext(ContractNegotiation.class, "Error finding ContractNegotiation: %s")
+                .orElseThrow(InvalidRequestException::new);
+    }
+
+    @Override
+    public ServiceResult<List<ContractNegotiation>> search(QuerySpec query) {
+        return responseQueue.getNextAsList(ContractNegotiation.class, "Error searching for ContractNegotiation: %s");
+    }
+
+    @Override
+    public String getState(String negotiationId) {
+        return responseQueue.getNext(String.class, "Error getting state of ContractNegotiation: %s")
+                .orElseThrow(InvalidRequestException::new);
+    }
+
+    @Override
+    public ContractAgreement getForNegotiation(String negotiationId) {
+        return responseQueue.getNext(ContractAgreement.class, "Error getting ContractAgreement: %s")
+                .orElseThrow(InvalidRequestException::new);
+    }
+
+    @Override
+    public ContractNegotiation initiateNegotiation(ContractRequest request) {
+        return responseQueue.getNext(ContractNegotiation.class, "Error initiating ContractNegotiation: %s")
+                .orElseThrow(InvalidRequestException::new);
+    }
+
+    @Override
+    public ServiceResult<Void> terminate(TerminateNegotiationCommand command) {
+        return responseQueue.getNext(Void.class, "Error terminating ContractAgreement: %s");
+    }
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/PolicyDefinitionServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/PolicyDefinitionServiceStub.java
new file mode 100644
index 000000000..fc214945b
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/PolicyDefinitionServiceStub.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock.services;
+
+import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition;
+import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService;
+import org.eclipse.edc.spi.query.QuerySpec;
+import org.eclipse.edc.spi.result.ServiceResult;
+import org.eclipse.edc.web.spi.exception.InvalidRequestException;
+import org.eclipse.tractusx.edc.mock.ResponseQueue;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public class PolicyDefinitionServiceStub extends AbstractServiceStub implements PolicyDefinitionService {
+
+    public PolicyDefinitionServiceStub(ResponseQueue responseQueue) {
+        super(responseQueue);
+    }
+
+    @Override
+    public PolicyDefinition findById(String policyId) {
+        return responseQueue.getNext(PolicyDefinition.class, "Error finding PolicyDefinition by id: %s")
+                .orElseThrow(InvalidRequestException::new);
+    }
+
+    @Override
+    public ServiceResult<List<PolicyDefinition>> search(QuerySpec query) {
+        return responseQueue.getNextAsList(PolicyDefinition.class, "Error executing PolicyDefinition search: %s");
+    }
+
+    @Override
+    public @NotNull ServiceResult<PolicyDefinition> deleteById(String policyId) {
+        return responseQueue.getNext(PolicyDefinition.class, "Error deleting PolicyDefinition: %s");
+    }
+
+    @Override
+    public @NotNull ServiceResult<PolicyDefinition> create(PolicyDefinition policy) {
+        return responseQueue.getNext(PolicyDefinition.class, "Error creating PolicyDefinition: %s");
+    }
+
+    @Override
+    public ServiceResult<PolicyDefinition> update(PolicyDefinition policy) {
+        return responseQueue.getNext(PolicyDefinition.class, "Error updating PolicyDefinition: %s");
+    }
+
+
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/TransferProcessServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/TransferProcessServiceStub.java
new file mode 100644
index 000000000..f02f750e2
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/TransferProcessServiceStub.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock.services;
+
+import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService;
+import org.eclipse.edc.connector.controlplane.transfer.spi.types.DeprovisionedResource;
+import org.eclipse.edc.connector.controlplane.transfer.spi.types.ProvisionResponse;
+import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess;
+import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest;
+import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.ResumeTransferCommand;
+import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.SuspendTransferCommand;
+import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.TerminateTransferCommand;
+import org.eclipse.edc.spi.query.QuerySpec;
+import org.eclipse.edc.spi.result.ServiceResult;
+import org.eclipse.edc.web.spi.exception.InvalidRequestException;
+import org.eclipse.tractusx.edc.mock.ResponseQueue;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+public class TransferProcessServiceStub extends AbstractServiceStub implements TransferProcessService {
+
+    public TransferProcessServiceStub(ResponseQueue responseQueue) {
+        super(responseQueue);
+    }
+
+    @Override
+    public @Nullable TransferProcess findById(String transferProcessId) {
+        return responseQueue.getNext(TransferProcess.class, "Error finding TransferProcess: %s").orElseThrow(f -> new InvalidRequestException(f.getFailureDetail()));
+    }
+
+    @Override
+    public ServiceResult<List<TransferProcess>> search(QuerySpec query) {
+        return responseQueue.getNextAsList(TransferProcess.class, "Error executing TransferProcess search: %s");
+    }
+
+    @Override
+    public @Nullable String getState(String transferProcessId) {
+        return responseQueue.getNext(String.class, "Error obtaining TransferProcess status: %s")
+                .orElseThrow(InvalidRequestException::new);
+    }
+
+    @Override
+    public @NotNull ServiceResult<Void> complete(String transferProcessId) {
+        return responseQueue.getNext(Void.class, "Error completing TransferProcess: %s");
+    }
+
+    @Override
+    public @NotNull ServiceResult<Void> terminate(TerminateTransferCommand command) {
+        return responseQueue.getNext(Void.class, "Error terminating TransferProcess: %s");
+    }
+
+    @Override
+    public @NotNull ServiceResult<Void> suspend(SuspendTransferCommand command) {
+        return responseQueue.getNext(Void.class, "Error suspending TransferProcess: %s");
+    }
+
+    @Override
+    public @NotNull ServiceResult<Void> resume(ResumeTransferCommand command) {
+        return responseQueue.getNext(Void.class, "Error resuming TransferProcess: %s");
+    }
+
+    @Override
+    public @NotNull ServiceResult<Void> deprovision(String transferProcessId) {
+        return responseQueue.getNext(Void.class, "Error deprovisioning TransferProcess: %s");
+    }
+
+    @Override
+    public @NotNull ServiceResult<TransferProcess> initiateTransfer(TransferRequest request) {
+        return responseQueue.getNext(TransferProcess.class, "Error initiating TransferProcess: %s");
+    }
+
+    @Override
+    public ServiceResult<Void> completeDeprovision(String transferProcessId, DeprovisionedResource resource) {
+        return responseQueue.getNext(Void.class, "Error completing/deprovisioning TransferProcess: %s");
+    }
+
+    @Override
+    public ServiceResult<Void> addProvisionedResource(String transferProcessId, ProvisionResponse response) {
+        return responseQueue.getNext(Void.class, "Error adding Provisioned resource to TransferProcess: %s");
+    }
+}
diff --git a/edc-tests/runtime/mock-connector/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-tests/runtime/mock-connector/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
new file mode 100644
index 000000000..3de76bc51
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
@@ -0,0 +1,21 @@
+#################################################################################
+#  Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+#
+#  See the NOTICE file(s) distributed with this work for additional
+#  information regarding copyright ownership.
+#
+#  This program and the accompanying materials are made available under the
+#  terms of the Apache License, Version 2.0 which is available at
+#  https://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
+#################################################################################
+
+org.eclipse.tractusx.edc.mock.MockServiceExtension
+
diff --git a/edc-tests/runtime/mock-connector/src/test/java/org/eclipse/tractusx/edc/mock/RecordedRequestTest.java b/edc-tests/runtime/mock-connector/src/test/java/org/eclipse/tractusx/edc/mock/RecordedRequestTest.java
new file mode 100644
index 000000000..e8e2853ee
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/test/java/org/eclipse/tractusx/edc/mock/RecordedRequestTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.mock;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset;
+import org.eclipse.edc.junit.testfixtures.TestUtils;
+import org.eclipse.edc.spi.result.ServiceFailure;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RecordedRequestTest {
+
+    private final ObjectMapper mapper = new ObjectMapper();
+
+    @BeforeEach
+    void setup() {
+        var module = new SimpleModule();
+        module.addDeserializer(ServiceFailure.class, new ServiceFailureDeserializer());
+        mapper.registerModule(module);
+    }
+
+    @Test
+    void verifySerDes() throws JsonProcessingException {
+        var json = TestUtils.getResourceFileContentAsString("asset.creation.json");
+        var rr = mapper.readValue(json, RecordedRequest.class);
+
+        assertThat(rr).isNotNull();
+        assertThat(rr.getInput()).isInstanceOf(Asset.class);
+        assertThat(rr.getOutput()).isInstanceOf(Asset.class);
+        assertThat(rr.getInputMatchType()).isEqualTo(MatchType.CLASS);
+        assertThat(rr.getFailure()).isNull();
+        assertThat(rr.getName()).isEqualTo("Asset Creation V3");
+        assertThat(rr.getDescription()).isEqualTo("test description");
+    }
+
+    @Test
+    void verifySerDes_withFailure() throws JsonProcessingException {
+        var json = TestUtils.getResourceFileContentAsString("asset.failure.json");
+        var rr = mapper.readValue(json, RecordedRequest.class);
+
+        assertThat(rr).isNotNull();
+        assertThat(rr.getInput()).isInstanceOf(Asset.class);
+        assertThat(rr.getOutput()).isNull();
+        assertThat(rr.getInputMatchType()).isEqualTo(MatchType.CLASS);
+        assertThat(rr.getName()).isEqualTo("Some failed asset request");
+        assertThat(rr.getDescription()).isEqualTo("test description");
+        assertThat(rr.getFailure()).isInstanceOf(ServiceFailure.class);
+        assertThat(rr.getFailure().getReason()).isEqualTo(ServiceFailure.Reason.UNAUTHORIZED);
+        assertThat(rr.getFailure().getMessages()).containsExactlyInAnyOrder("message 1", "message 2");
+    }
+}
\ No newline at end of file
diff --git a/edc-tests/runtime/mock-connector/src/test/resources/asset.creation.json b/edc-tests/runtime/mock-connector/src/test/resources/asset.creation.json
new file mode 100644
index 000000000..e22569a12
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/test/resources/asset.creation.json
@@ -0,0 +1,24 @@
+{
+  "name": "Asset Creation V3",
+  "description": "test description",
+  "input": {
+    "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset",
+    "data": {
+      "id": "asset-1",
+      "properties": {
+        "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json",
+        "https://w3id.org/edc/v0.0.1/ns/prop1": "value1"
+      }
+    }
+  },
+  "output": {
+    "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset",
+    "data": {
+      "id": "asset-1",
+      "properties": {
+        "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json",
+        "https://w3id.org/edc/v0.0.1/ns/prop1": "value1"
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/edc-tests/runtime/mock-connector/src/test/resources/asset.failure.json b/edc-tests/runtime/mock-connector/src/test/resources/asset.failure.json
new file mode 100644
index 000000000..22af58351
--- /dev/null
+++ b/edc-tests/runtime/mock-connector/src/test/resources/asset.failure.json
@@ -0,0 +1,24 @@
+{
+  "name": "Some failed asset request",
+  "description": "test description",
+  "input": {
+    "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset",
+    "data": {
+      "id": "asset-1",
+      "properties": {
+        "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json",
+        "https://w3id.org/edc/v0.0.1/ns/prop1": "value1"
+      }
+    }
+  },
+  "output": {
+    "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset"
+  },
+  "failure": {
+    "reason": "UNAUTHORIZED",
+    "messages": [
+      "message 1",
+      "message 2"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 3f5844e16..561d92dea 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -93,6 +93,7 @@ edc-lib-cryptocommon = { module = "org.eclipse.edc:crypto-common-lib", version.r
 edc-lib-http = { module = "org.eclipse.edc:http-lib", version.ref = "edc" }
 edc-lib-jersey-providers = { module = "org.eclipse.edc:jersey-providers-lib", version.ref = "edc" }
 edc-lib-jsonld = { module = "org.eclipse.edc:json-ld-lib", version.ref = "edc" }
+edc-lib-json = { module = "org.eclipse.edc:json-lib", version.ref = "edc" }
 edc-lib-jws2020 = { module = "org.eclipse.edc:jws2020-lib", version.ref = "edc" }
 edc-lib-policyengine = { module = "org.eclipse.edc:policy-engine-lib", version.ref = "edc" }
 edc-lib-query = { module = "org.eclipse.edc:query-lib", version.ref = "edc" }
diff --git a/samples/testing-with-mocked-connector/build.gradle.kts b/samples/testing-with-mocked-connector/build.gradle.kts
new file mode 100644
index 000000000..32607c4d4
--- /dev/null
+++ b/samples/testing-with-mocked-connector/build.gradle.kts
@@ -0,0 +1,38 @@
+/********************************************************************************
+ * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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
+ ********************************************************************************/
+
+plugins {
+    `java-library`
+    `java-test-fixtures`
+}
+
+dependencies {
+    testImplementation(testFixtures(project(":edc-tests:edc-controlplane:fixtures")))
+
+    testImplementation(libs.testcontainers.junit)
+    testImplementation(libs.netty.mockserver)
+    testImplementation(libs.edc.junit)
+    testImplementation(libs.restAssured)
+    testImplementation(libs.awaitility)
+}
+
+// do not publish
+edcBuild {
+    publish.set(false)
+}
diff --git a/samples/testing-with-mocked-connector/src/test/java/org/eclipse/tractusx/edc/samples/mockedc/UseMockConnectorSampleTest.java b/samples/testing-with-mocked-connector/src/test/java/org/eclipse/tractusx/edc/samples/mockedc/UseMockConnectorSampleTest.java
new file mode 100644
index 000000000..853ac577e
--- /dev/null
+++ b/samples/testing-with-mocked-connector/src/test/java/org/eclipse/tractusx/edc/samples/mockedc/UseMockConnectorSampleTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://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.eclipse.tractusx.edc.samples.mockedc;
+
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import jakarta.json.JsonArray;
+import org.eclipse.edc.junit.annotations.ComponentTest;
+import org.eclipse.edc.junit.testfixtures.TestUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import static io.restassured.RestAssured.given;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * This example demonstrates how to use the Mock-Connector as a drop-in replacement runtime for testing client code that uses EDC's
+ * Management API. While this is written in Java, the concepts are easily translatable into any language where test containers are
+ * supported.
+ */
+@Testcontainers
+@ComponentTest
+public class UseMockConnectorSampleTest {
+    @Container
+    protected static GenericContainer<?> edcContainer = new GenericContainer<>("mock-connector")
+            .withEnv("WEB_HTTP_PORT", "8080")
+            .withEnv("WEB_HTTP_PATH", "/api")
+            .withEnv("WEB_HTTP_MANAGEMENT_PORT", "8081")
+            .withEnv("WEB_HTTP_MANAGEMENT_PATH", "/api/management")
+            .withExposedPorts(8080, 8081);
+    private int managementPort;
+    private int defaultPort;
+
+    @BeforeEach
+    void setup() {
+        managementPort = edcContainer.getMappedPort(8081);
+        defaultPort = edcContainer.getMappedPort(8080);
+    }
+
+    @Test
+    void test_getAsset() {
+        //prime the mock - post a RecordedRequest
+        setupNextResponse("asset.request.json");
+
+        // perform the actual Asset API request. In a real test scenario, this would be the client code we're testing, i.e. the
+        // System-under-Test (SuT).
+        var assetArray = mgmtRequest()
+                .contentType(ContentType.JSON)
+                .body("""
+                        {
+                          "@context": {
+                            "@vocab": "https://w3id.org/edc/v0.0.1/ns/"
+                          },
+                        "@type": "QuerySpec"
+                        }
+                        """)
+                .post("/v3/assets/request")
+                .then()
+                .log().ifError()
+                .statusCode(200)
+                .extract().body().as(JsonArray.class);
+
+        // assert the response
+        assertThat(assetArray).hasSize(1);
+        assertThat(assetArray.get(0).asJsonObject().get("properties"))
+                .hasFieldOrProperty("prop1")
+                .hasFieldOrProperty("id")
+                .hasFieldOrProperty("contenttype");
+    }
+
+    @Test
+    void test_apiNotAuthenticated_expect400() {
+        //prime the mock - post a RecordedRequest
+        setupNextResponse("asset.creation.failure.json");
+
+        // perform the actual Asset API request. In a real test scenario, this would be the client code we're testing, i.e. the
+        // System-under-Test (SuT).
+        var assetArray = mgmtRequest()
+                .contentType(ContentType.JSON)
+                .body("""
+                        {
+                          "@context": {
+                            "@vocab": "https://w3id.org/edc/v0.0.1/ns/"
+                          },
+                        "@type": "QuerySpec"
+                        }
+                        """)
+                .post("/v3/assets/request")
+                .then()
+                .log().ifError()
+                .statusCode(400)
+                .extract().body().as(JsonArray.class);
+
+        // assert the response contains error information
+        assertThat(assetArray).hasSize(1);
+        var errorObject = assetArray.get(0).asJsonObject();
+        assertThat(errorObject.get("message").toString()).contains("This user is not authorized, This is just a second error message");
+    }
+
+    private void setupNextResponse(String resourceFileName) {
+        var json = TestUtils.getResourceFileContentAsString(resourceFileName);
+
+        apiRequest()
+                .contentType(ContentType.JSON)
+                .body(json)
+                .post("/instrumentation")
+                .then()
+                .log().ifError()
+                .statusCode(204);
+    }
+
+    private RequestSpecification apiRequest() {
+        return given()
+                .baseUri("http://localhost:" + defaultPort + "/api")
+                .when();
+    }
+
+    private RequestSpecification mgmtRequest() {
+        return given()
+                .baseUri("http://localhost:" + managementPort + "/api/management")
+                .when();
+    }
+}
diff --git a/samples/testing-with-mocked-connector/src/test/resources/asset.creation.failure.json b/samples/testing-with-mocked-connector/src/test/resources/asset.creation.failure.json
new file mode 100644
index 000000000..32d5b8459
--- /dev/null
+++ b/samples/testing-with-mocked-connector/src/test/resources/asset.creation.failure.json
@@ -0,0 +1,24 @@
+{
+  "name": "Some failed asset request",
+  "description": "test description",
+  "input": {
+    "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset",
+    "data": {
+      "id": "asset-1",
+      "properties": {
+        "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json",
+        "https://w3id.org/edc/v0.0.1/ns/prop1": "value1"
+      }
+    }
+  },
+  "output": {
+    "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset"
+  },
+  "failure": {
+    "reason": "UNAUTHORIZED",
+    "messages": [
+      "This user is not authorized",
+      "This is just a second error message"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/samples/testing-with-mocked-connector/src/test/resources/asset.creation.json b/samples/testing-with-mocked-connector/src/test/resources/asset.creation.json
new file mode 100644
index 000000000..1f26e3e33
--- /dev/null
+++ b/samples/testing-with-mocked-connector/src/test/resources/asset.creation.json
@@ -0,0 +1,23 @@
+{
+  "name": "Asset Creation V3",
+  "input": {
+    "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset",
+    "data": {
+      "id": "asset-1",
+      "properties": {
+        "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json",
+        "https://w3id.org/edc/v0.0.1/ns/prop1": "value1"
+      }
+    }
+  },
+  "output": {
+    "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset",
+    "data": {
+      "id": "asset-1",
+      "properties": {
+        "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json",
+        "https://w3id.org/edc/v0.0.1/ns/prop1": "value1"
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/samples/testing-with-mocked-connector/src/test/resources/asset.request.json b/samples/testing-with-mocked-connector/src/test/resources/asset.request.json
new file mode 100644
index 000000000..1f3dcafd8
--- /dev/null
+++ b/samples/testing-with-mocked-connector/src/test/resources/asset.request.json
@@ -0,0 +1,22 @@
+{
+  "name": "Asset Query v3",
+  "input": {
+    "class": "org.eclipse.edc.spi.query.QuerySpec",
+    "data": {
+      "offset": 0,
+      "sortOrder": "DESC"
+    }
+  },
+  "output": {
+    "class": "[Lorg.eclipse.edc.connector.controlplane.asset.spi.domain.Asset;",
+    "data": [
+      {
+        "id": "asset-1",
+        "properties": {
+          "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json",
+          "https://w3id.org/edc/v0.0.1/ns/prop1": "value1"
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/samples/testing-with-mocked-connector/src/test/resources/contractdef.creation.json b/samples/testing-with-mocked-connector/src/test/resources/contractdef.creation.json
new file mode 100644
index 000000000..855ca8b1a
--- /dev/null
+++ b/samples/testing-with-mocked-connector/src/test/resources/contractdef.creation.json
@@ -0,0 +1,33 @@
+{
+  "name": "Asset Creation V3",
+  "input": {
+    "class": "org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition",
+    "data": {
+      "id": "asset-1",
+      "accessPolicyId": "test-policy1",
+      "contractPolicyId": "test-policy2",
+      "assetsSelector": [
+        {
+          "operandLeft": "id",
+          "operator": "=",
+          "operandRight": "some-asset-id"
+        }
+      ]
+    }
+  },
+  "output": {
+    "class": "org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition",
+    "data": {
+      "id": "asset-1",
+      "accessPolicyId": "test-policy1",
+      "contractPolicyId": "test-policy2",
+      "assetsSelector": [
+        {
+          "operandLeft": "id",
+          "operator": "=",
+          "operandRight": "some-asset-id"
+        }
+      ]
+    }
+  }
+}
\ No newline at end of file
diff --git a/samples/testing-with-mocked-connector/src/test/resources/transferprocess.request.json b/samples/testing-with-mocked-connector/src/test/resources/transferprocess.request.json
new file mode 100644
index 000000000..898eea501
--- /dev/null
+++ b/samples/testing-with-mocked-connector/src/test/resources/transferprocess.request.json
@@ -0,0 +1,21 @@
+{
+  "name": "TransferProcess Query v3",
+  "input": {
+    "class": "org.eclipse.edc.spi.query.QuerySpec",
+    "data": {
+      "offset": 0,
+      "sortOrder": "DESC"
+    }
+  },
+  "output": {
+    "class": "[Lorg.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess;",
+    "data": [
+      {
+        "id": "transferprocess-1",
+        "type": "CONSUMER",
+        "assetId": "some-asset-id",
+        "contractId": "some-contract-id"
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 980a606c5..9f5dc1e2b 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -67,6 +67,7 @@ include(":edc-tests:edc-controlplane:policy-tests")
 include(":edc-tests:edc-controlplane:fixtures")
 include(":edc-tests:runtime:extensions")
 include(":edc-tests:runtime:runtime-memory")
+include(":edc-tests:runtime:mock-connector")
 include(":edc-tests:runtime:dataplane-cloud")
 include(":edc-tests:runtime:runtime-postgresql")
 include(":edc-tests:runtime:iatp:runtime-memory-iatp-ih")
@@ -93,6 +94,7 @@ include(":edc-dataplane:edc-dataplane-hashicorp-vault")
 
 
 include(":samples:multi-tenancy")
+include(":samples:testing-with-mocked-connector")
 
 
 // this is needed to have access to snapshot builds of plugins